projects_spec.rb 161 KB
Newer Older
1 2
# frozen_string_literal: true

Nihad Abbasov's avatar
Nihad Abbasov committed
3 4
require 'spec_helper'

5
RSpec.shared_examples 'languages and percentages JSON response' do
6
  let(:expected_languages) { project.repository.languages.to_h { |language| language.values_at(:label, :value) } }
7

8 9 10 11 12 13 14 15 16
  before do
    allow(project.repository).to receive(:languages).and_return(
      [{ value: 66.69, label: "Ruby", color: "#701516", highlight: "#701516" },
       { value: 22.98, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" },
       { value: 7.91, label: "HTML", color: "#e34c26", highlight: "#e34c26" },
       { value: 2.42, label: "CoffeeScript", color: "#244776", highlight: "#244776" }]
    )
  end

17
  context "when the languages haven't been detected yet" do
18
    it 'returns expected language values', :sidekiq_might_not_need_inline do
19
      get api("/projects/#{project.id}/languages", user)
20

21 22
      expect(response).to have_gitlab_http_status(:ok)
      expect(json_response).to eq({})
23

24 25 26
      get api("/projects/#{project.id}/languages", user)

      expect(response).to have_gitlab_http_status(:ok)
27
      expect(Gitlab::Json.parse(response.body)).to eq(expected_languages)
28
    end
29
  end
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46

  context 'when the languages were detected before' do
    before do
      Projects::DetectRepositoryLanguagesService.new(project, project.owner).execute
    end

    it 'returns the detection from the database' do
      # Allow this to happen once, so the expected languages can be determined
      expect(project.repository).to receive(:languages).once

      get api("/projects/#{project.id}/languages", user)

      expect(response).to have_gitlab_http_status(:ok)
      expect(json_response).to eq(expected_languages)
      expect(json_response.count).to be > 1
    end
  end
47 48
end

49
RSpec.describe API::Projects do
50 51
  include ProjectForksHelper

52 53 54 55
  let_it_be(:user) { create(:user) }
  let_it_be(:user2) { create(:user) }
  let_it_be(:user3) { create(:user) }
  let_it_be(:admin) { create(:admin) }
56
  let_it_be(:project, reload: true) { create(:project, :repository, create_branch: 'something_else', namespace: user.namespace) }
57 58
  let_it_be(:project2, reload: true) { create(:project, namespace: user.namespace) }
  let_it_be(:project_member) { create(:project_member, :developer, user: user3, project: project) }
59
  let_it_be(:user4) { create(:user, username: 'user.withdot') }
60
  let_it_be(:project3, reload: true) do
61
    create(:project,
62
    :private,
63
    :repository,
64 65 66 67 68 69
    name: 'second_project',
    path: 'second_project',
    creator_id: user.id,
    namespace: user.namespace,
    merge_requests_enabled: false,
    issues_enabled: false, wiki_enabled: false,
winniehell's avatar
winniehell committed
70
    builds_enabled: false,
71
    snippets_enabled: false)
72
  end
73

74
  let_it_be(:project_member2) do
75 76 77
    create(:project_member,
    user: user4,
    project: project3,
78
    access_level: ProjectMember::MAINTAINER)
79
  end
80

81
  let_it_be(:project4, reload: true) do
82
    create(:project,
83 84 85 86 87 88
    name: 'third_project',
    path: 'third_project',
    creator_id: user4.id,
    namespace: user4.namespace)
  end

89 90
  let(:user_projects) { [public_project, project, project2, project3] }

91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
  shared_context 'with language detection' do
    let(:ruby) { create(:programming_language, name: 'Ruby') }
    let(:javascript) { create(:programming_language, name: 'JavaScript') }
    let(:html) { create(:programming_language, name: 'HTML') }

    let(:mock_repo_languages) do
      {
        project => { ruby => 0.5, html => 0.5 },
        project3 => { html => 0.7, javascript => 0.3 }
      }
    end

    before do
      mock_repo_languages.each do |proj, lang_shares|
        lang_shares.each do |lang, share|
          create(:repository_language, project: proj, programming_language: lang, share: share)
        end
      end
    end
  end

112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
  shared_examples_for 'create project with default branch parameter' do
    let(:params) { { name: 'Foo Project', initialize_with_readme: true, default_branch: default_branch } }
    let(:default_branch) { 'main' }

    it 'creates project with provided default branch name' do
      expect { request }.to change { Project.count }.by(1)
      expect(response).to have_gitlab_http_status(:created)

      project = Project.find(json_response['id'])
      expect(project.default_branch).to eq(default_branch)
    end

    context 'when branch name is empty' do
      let(:default_branch) { '' }

      it 'creates project with a default project branch name' do
        expect { request }.to change { Project.count }.by(1)
        expect(response).to have_gitlab_http_status(:created)

        project = Project.find(json_response['id'])
        expect(project.default_branch).to eq('master')
      end
    end

    context 'when initialize with readme is not set' do
      let(:params) { super().merge(initialize_with_readme: nil) }

      it 'creates project with a default project branch name' do
        expect { request }.to change { Project.count }.by(1)
        expect(response).to have_gitlab_http_status(:created)

        project = Project.find(json_response['id'])
        expect(project.default_branch).to be_nil
      end
    end
  end

149
  describe 'GET /projects' do
150 151
    shared_examples_for 'projects response' do
      it 'returns an array of projects' do
blackst0ne's avatar
blackst0ne committed
152
        get api('/projects', current_user), params: filter
153

154
        expect(response).to have_gitlab_http_status(:ok)
155
        expect(response).to include_pagination_headers
156 157 158
        expect(json_response).to be_an Array
        expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id))
      end
159 160

      it 'returns the proper security headers' do
blackst0ne's avatar
blackst0ne committed
161
        get api('/projects', current_user), params: filter
162 163 164

        expect(response).to include_security_headers
      end
165 166
    end

167 168 169
    shared_examples_for 'projects response without N + 1 queries' do |threshold|
      let(:additional_project) { create(:project, :public) }

170
      it 'avoids N + 1 queries' do
171 172
        get api('/projects', current_user)

173
        control = ActiveRecord::QueryRecorder.new do
174
          get api('/projects', current_user)
175
        end
176

177
        additional_project
178 179 180

        expect do
          get api('/projects', current_user)
181
        end.not_to exceed_query_limit(control).with_threshold(threshold)
182 183 184
      end
    end

185
    let_it_be(:public_project) { create(:project, :public, name: 'public_project') }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
186

187
    context 'when unauthenticated' do
188
      it_behaves_like 'projects response' do
189 190 191 192 193
        let(:filter) { { search: project.name } }
        let(:current_user) { user }
        let(:projects) { [project] }
      end

194
      it_behaves_like 'projects response without N + 1 queries', 1 do
195
        let(:current_user) { nil }
196
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
197 198
    end

199
    context 'when authenticated as regular user' do
200
      it_behaves_like 'projects response' do
201
        let(:filter) { {} }
202
        let(:current_user) { user }
203
        let(:projects) { user_projects }
Nihad Abbasov's avatar
Nihad Abbasov committed
204
      end
205

206
      it_behaves_like 'projects response without N + 1 queries', 0 do
207 208 209
        let(:current_user) { user }
      end

210 211 212 213 214 215 216 217 218 219 220 221
      it 'includes container_registry_access_level', :aggregate_failures do
        project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED)

        get api('/projects', user)
        project_response = json_response.find { |p| p['id'] == project.id }

        expect(response).to have_gitlab_http_status(:ok)
        expect(json_response).to be_an Array
        expect(project_response['container_registry_access_level']).to eq('disabled')
        expect(project_response['container_registry_enabled']).to eq(false)
      end

222 223
      context 'when some projects are in a group' do
        before do
224
          create(:project, :public, group: create(:group))
225 226
        end

227
        it_behaves_like 'projects response without N + 1 queries', 0 do
228
          let(:current_user) { user }
229
          let(:additional_project) { create(:project, :public, group: create(:group)) }
230 231 232
        end
      end

233
      it 'includes correct value of container_registry_enabled', :aggregate_failures do
234
        project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED)
235 236 237 238 239 240 241 242 243

        get api('/projects', user)
        project_response = json_response.find { |p| p['id'] == project.id }

        expect(response).to have_gitlab_http_status(:ok)
        expect(json_response).to be_an Array
        expect(project_response['container_registry_enabled']).to eq(false)
      end

244
      it 'includes project topics' do
245
        get api('/projects', user)
246

247
        expect(response).to have_gitlab_http_status(:ok)
248
        expect(response).to include_pagination_headers
249
        expect(json_response).to be_an Array
250 251
        expect(json_response.first.keys).to include('tag_list') # deprecated in favor of 'topics'
        expect(json_response.first.keys).to include('topics')
252
      end
253

254
      it 'includes open_issues_count' do
255
        get api('/projects', user)
256

257
        expect(response).to have_gitlab_http_status(:ok)
258
        expect(response).to include_pagination_headers
259 260 261 262
        expect(json_response).to be_an Array
        expect(json_response.first.keys).to include('open_issues_count')
      end

263
      it 'does not include projects marked for deletion' do
264
        project.update!(pending_delete: true)
265 266 267

        get api('/projects', user)

268
        expect(response).to have_gitlab_http_status(:ok)
269 270 271 272
        expect(json_response).to be_an Array
        expect(json_response.map { |p| p['id'] }).not_to include(project.id)
      end

273
      it 'does not include open_issues_count if issues are disabled' do
274
        project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
275 276

        get api('/projects', user)
277

278
        expect(response).to have_gitlab_http_status(:ok)
279
        expect(response).to include_pagination_headers
280
        expect(json_response).to be_an Array
281
        expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
282 283
      end

284
      context 'filter by topic (column topic_list)' do
285
        before do
286
          project.update!(topic_list: %w(ruby javascript))
287 288 289 290 291 292 293 294 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 322 323 324 325 326 327 328 329
        end

        it 'returns no projects' do
          get api('/projects', user), params: { topic: 'foo' }

          expect(response).to have_gitlab_http_status(:ok)
          expect(response).to include_pagination_headers
          expect(json_response).to be_empty
        end

        it 'returns matching project for a single topic' do
          get api('/projects', user), params: { topic: 'ruby' }

          expect(response).to have_gitlab_http_status(:ok)
          expect(response).to include_pagination_headers
          expect(json_response).to contain_exactly a_hash_including('id' => project.id)
        end

        it 'returns matching project for multiple topics' do
          get api('/projects', user), params: { topic: 'ruby, javascript' }

          expect(response).to have_gitlab_http_status(:ok)
          expect(response).to include_pagination_headers
          expect(json_response).to contain_exactly a_hash_including('id' => project.id)
        end

        it 'returns no projects if project match only some topic' do
          get api('/projects', user), params: { topic: 'ruby, foo' }

          expect(response).to have_gitlab_http_status(:ok)
          expect(response).to include_pagination_headers
          expect(json_response).to be_empty
        end

        it 'ignores topic if it is empty' do
          get api('/projects', user), params: { topic: '' }

          expect(response).to have_gitlab_http_status(:ok)
          expect(response).to include_pagination_headers
          expect(json_response).to be_present
        end
      end

330 331 332 333 334 335
      context 'and with_issues_enabled=true' do
        it 'only returns projects with issues enabled' do
          project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)

          get api('/projects?with_issues_enabled=true', user)

336
          expect(response).to have_gitlab_http_status(:ok)
337 338 339 340 341 342
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.map { |p| p['id'] }).not_to include(project.id)
        end
      end

343 344 345
      it "does not include statistics by default" do
        get api('/projects', user)

346
        expect(response).to have_gitlab_http_status(:ok)
347
        expect(response).to include_pagination_headers
348 349
        expect(json_response).to be_an Array
        expect(json_response.first).not_to include('statistics')
350 351
      end

352
      it "includes statistics if requested" do
blackst0ne's avatar
blackst0ne committed
353
        get api('/projects', user), params: { statistics: true }
354

355
        expect(response).to have_gitlab_http_status(:ok)
356
        expect(response).to include_pagination_headers
357
        expect(json_response).to be_an Array
358

359
        statistics = json_response.find { |p| p['id'] == project.id }['statistics']
360
        expect(statistics).to be_present
361
        expect(statistics).to include('commit_count', 'storage_size', 'repository_size', 'wiki_size', 'lfs_objects_size', 'job_artifacts_size', 'snippets_size', 'packages_size')
362 363
      end

364 365 366
      it "does not include license by default" do
        get api('/projects', user)

367
        expect(response).to have_gitlab_http_status(:ok)
368 369 370 371 372 373
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response.first).not_to include('license', 'license_url')
      end

      it "does not include license if requested" do
blackst0ne's avatar
blackst0ne committed
374
        get api('/projects', user), params: { license: true }
375

376
        expect(response).to have_gitlab_http_status(:ok)
377 378 379 380 381
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response.first).not_to include('license', 'license_url')
      end

382
      context 'when external issue tracker is enabled' do
383
        let!(:jira_integration) { create(:jira_integration, project: project) }
384 385 386 387

        it 'includes open_issues_count' do
          get api('/projects', user)

388
          expect(response).to have_gitlab_http_status(:ok)
389 390 391 392 393 394 395 396 397 398 399
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.first.keys).to include('open_issues_count')
          expect(json_response.find { |hash| hash['id'] == project.id }.keys).to include('open_issues_count')
        end

        it 'does not include open_issues_count if issues are disabled' do
          project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)

          get api('/projects', user)

400
          expect(response).to have_gitlab_http_status(:ok)
401 402 403 404 405 406
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
        end
      end

407
      context 'and with simple=true' do
tiagonbotelho's avatar
tiagonbotelho committed
408
        it 'returns a simplified version of all the projects' do
409
          get api('/projects?simple=true', user)
tiagonbotelho's avatar
tiagonbotelho committed
410

411
          expect(response).to have_gitlab_http_status(:ok)
412
          expect(response).to include_pagination_headers
413
          expect(response).to match_response_schema('public_api/v4/projects')
414 415 416
        end
      end

417 418 419 420 421 422
      context 'and using archived' do
        let!(:archived_project) { create(:project, creator_id: user.id, namespace: user.namespace, archived: true) }

        it 'returns archived projects' do
          get api('/projects?archived=true', user)

423
          expect(response).to have_gitlab_http_status(:ok)
424 425 426 427 428 429 430 431 432
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.length).to eq(Project.public_or_visible_to_user(user).where(archived: true).size)
          expect(json_response.map { |project| project['id'] }).to include(archived_project.id)
        end

        it 'returns non-archived projects' do
          get api('/projects?archived=false', user)

433
          expect(response).to have_gitlab_http_status(:ok)
434 435 436 437 438 439 440 441 442
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.length).to eq(Project.public_or_visible_to_user(user).where(archived: false).size)
          expect(json_response.map { |project| project['id'] }).not_to include(archived_project.id)
        end

        it 'returns every project' do
          get api('/projects', user)

443
          expect(response).to have_gitlab_http_status(:ok)
444 445 446 447 448 449
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.map { |project| project['id'] }).to contain_exactly(*Project.public_or_visible_to_user(user).pluck(:id))
        end
      end

450
      context 'and using search' do
451 452 453 454 455 456
        it_behaves_like 'projects response' do
          let(:filter) { { search: project.name } }
          let(:current_user) { user }
          let(:projects) { [project] }
        end
      end
457

458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
      context 'and using search and search_namespaces is true' do
        let(:group) { create(:group) }
        let!(:project_in_group) { create(:project, group: group) }

        before do
          group.add_guest(user)
        end

        it_behaves_like 'projects response' do
          let(:filter) { { search: group.name, search_namespaces: true } }
          let(:current_user) { user }
          let(:projects) { [project_in_group] }
        end
      end

473 474 475 476
      context 'and using id_after' do
        it_behaves_like 'projects response' do
          let(:filter) { { id_after: project2.id } }
          let(:current_user) { user }
477
          let(:projects) { user_projects.select { |p| p.id > project2.id } }
478
        end
479 480 481 482 483

        context 'regression: empty string is ignored' do
          it_behaves_like 'projects response' do
            let(:filter) { { id_after: '' } }
            let(:current_user) { user }
484
            let(:projects) { user_projects }
485 486
          end
        end
487 488 489 490 491 492
      end

      context 'and using id_before' do
        it_behaves_like 'projects response' do
          let(:filter) { { id_before: project2.id } }
          let(:current_user) { user }
493
          let(:projects) { user_projects.select { |p| p.id < project2.id } }
494
        end
495 496 497 498 499

        context 'regression: empty string is ignored' do
          it_behaves_like 'projects response' do
            let(:filter) { { id_before: '' } }
            let(:current_user) { user }
500
            let(:projects) { user_projects }
501 502
          end
        end
503 504
      end

505 506 507 508
      context 'and using both id_after and id_before' do
        it_behaves_like 'projects response' do
          let(:filter) { { id_before: project2.id, id_after: public_project.id } }
          let(:current_user) { user }
509
          let(:projects) { user_projects.select { |p| p.id < project2.id && p.id > public_project.id } }
510 511 512
        end
      end

513
      context 'and membership=true' do
514
        it_behaves_like 'projects response' do
515
          let(:filter) { { membership: true } }
516 517
          let(:current_user) { user }
          let(:projects) { [project, project2, project3] }
518 519 520
        end
      end

Josh Frye's avatar
Josh Frye committed
521
      context 'and using the visibility filter' do
522
        it 'filters based on private visibility param' do
blackst0ne's avatar
blackst0ne committed
523
          get api('/projects', user), params: { visibility: 'private' }
524

525
          expect(response).to have_gitlab_http_status(:ok)
526
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
527
          expect(json_response).to be_an Array
528
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id, project2.id, project3.id)
Josh Frye's avatar
Josh Frye committed
529 530
        end

531
        it 'filters based on internal visibility param' do
532 533
          project2.update_attribute(:visibility_level, Gitlab::VisibilityLevel::INTERNAL)

blackst0ne's avatar
blackst0ne committed
534
          get api('/projects', user), params: { visibility: 'internal' }
535

536
          expect(response).to have_gitlab_http_status(:ok)
537
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
538
          expect(json_response).to be_an Array
539
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project2.id)
Josh Frye's avatar
Josh Frye committed
540 541
        end

542
        it 'filters based on public visibility param' do
blackst0ne's avatar
blackst0ne committed
543
          get api('/projects', user), params: { visibility: 'public' }
544

545
          expect(response).to have_gitlab_http_status(:ok)
546
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
547
          expect(json_response).to be_an Array
548
          expect(json_response.map { |p| p['id'] }).to contain_exactly(public_project.id)
Josh Frye's avatar
Josh Frye committed
549 550 551
        end
      end

552 553 554 555 556 557
      context 'and using the programming language filter' do
        include_context 'with language detection'

        it 'filters case-insensitively by programming language' do
          get api('/projects', user), params: { with_programming_language: 'javascript' }

558
          expect(response).to have_gitlab_http_status(:ok)
559 560 561 562 563 564
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project3.id)
        end
      end

565
      context 'and using sorting' do
566
        it 'returns the correct order when sorted by id' do
blackst0ne's avatar
blackst0ne committed
567
          get api('/projects', user), params: { order_by: 'id', sort: 'desc' }
568

569
          expect(response).to have_gitlab_http_status(:ok)
570
          expect(response).to include_pagination_headers
571
          expect(json_response).to be_an Array
572
          expect(json_response.map { |p| p['id'] }).to eq(user_projects.map(&:id).sort.reverse)
573 574
        end
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
575

576 577
      context 'and with owned=true' do
        it 'returns an array of projects the user owns' do
blackst0ne's avatar
blackst0ne committed
578
          get api('/projects', user4), params: { owned: true }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
579

580
          expect(response).to have_gitlab_http_status(:ok)
581
          expect(response).to include_pagination_headers
582 583 584
          expect(json_response).to be_an Array
          expect(json_response.first['name']).to eq(project4.name)
          expect(json_response.first['owner']['username']).to eq(user4.username)
585
        end
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604

        context 'when admin creates a project' do
          before do
            group = create(:group)
            project_create_opts = {
              name: 'GitLab',
              namespace_id: group.id
            }

            Projects::CreateService.new(admin, project_create_opts).execute
          end

          it 'does not list as owned project for admin' do
            get api('/projects', admin), params: { owned: true }

            expect(response).to have_gitlab_http_status(:ok)
            expect(json_response).to be_empty
          end
        end
605 606
      end

607
      context 'and with starred=true' do
608
        let(:public_project) { create(:project, :public) }
609

610
        before do
611
          user3.update!(starred_projects: [project, project2, project3, public_project])
612
        end
Marin Jankovski's avatar
Marin Jankovski committed
613

614
        it 'returns the starred projects viewable by the user' do
blackst0ne's avatar
blackst0ne committed
615
          get api('/projects', user3), params: { starred: true }
616

617
          expect(response).to have_gitlab_http_status(:ok)
618
          expect(response).to include_pagination_headers
619 620
          expect(json_response).to be_an Array
          expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
621
        end
622 623
      end

624
      context 'and with all query parameters' do
625
        let!(:project5) { create(:project, :public, path: 'gitlab5', namespace: create(:namespace)) }
626
        let!(:project6) { create(:project, :public, namespace: user.namespace) }
627 628 629
        let!(:project7) { create(:project, :public, path: 'gitlab7', namespace: user.namespace) }
        let!(:project8) { create(:project, path: 'gitlab8', namespace: user.namespace) }
        let!(:project9) { create(:project, :public, path: 'gitlab9') }
630

631
        before do
632
          user.update!(starred_projects: [project5, project7, project8, project9])
633
        end
634

635
        context 'including owned filter' do
636
          it 'returns only projects that satisfy all query parameters' do
blackst0ne's avatar
blackst0ne committed
637
            get api('/projects', user), params: { visibility: 'public', owned: true, starred: true, search: 'gitlab' }
638

639
            expect(response).to have_gitlab_http_status(:ok)
640 641 642 643 644 645
            expect(response).to include_pagination_headers
            expect(json_response).to be_an Array
            expect(json_response.size).to eq(1)
            expect(json_response.first['id']).to eq(project7.id)
          end
        end
646

647
        context 'including membership filter' do
648 649 650 651
          before do
            create(:project_member,
                   user: user,
                   project: project5,
652
                   access_level: ProjectMember::MAINTAINER)
653
          end
654

655
          it 'returns only projects that satisfy all query parameters' do
blackst0ne's avatar
blackst0ne committed
656
            get api('/projects', user), params: { visibility: 'public', membership: true, starred: true, search: 'gitlab' }
657

658
            expect(response).to have_gitlab_http_status(:ok)
659 660 661
            expect(response).to include_pagination_headers
            expect(json_response).to be_an Array
            expect(json_response.size).to eq(2)
662
            expect(json_response.map { |project| project['id'] }).to contain_exactly(project5.id, project7.id)
663
          end
664
        end
665
      end
666 667 668

      context 'and with min_access_level' do
        before do
669
          project2.add_maintainer(user2)
670 671 672 673
          project3.add_developer(user2)
          project4.add_reporter(user2)
        end

674
        it 'returns an array of projects the user has at least developer access' do
blackst0ne's avatar
blackst0ne committed
675
          get api('/projects', user2), params: { min_access_level: 30 }
676

677
          expect(response).to have_gitlab_http_status(:ok)
678 679 680 681 682
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.map { |project| project['id'] }).to contain_exactly(project2.id, project3.id)
        end
      end
683
    end
684

685
    context 'when authenticated as a different user' do
686
      it_behaves_like 'projects response' do
687
        let(:filter) { {} }
688 689 690
        let(:current_user) { user2 }
        let(:projects) { [public_project] }
      end
691 692 693 694 695 696 697

      context 'and with_issues_enabled=true' do
        it 'does not return private issue projects' do
          project.project_feature.update_attribute(:issues_access_level, ProjectFeature::PRIVATE)

          get api('/projects?with_issues_enabled=true', user2)

698
          expect(response).to have_gitlab_http_status(:ok)
699 700 701 702 703
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.map { |p| p['id'] }).not_to include(project.id)
        end
      end
704 705
    end

706 707
    context 'when authenticated as admin' do
      it_behaves_like 'projects response' do
708
        let(:filter) { {} }
709 710 711
        let(:current_user) { admin }
        let(:projects) { Project.all }
      end
712
    end
713

714 715 716 717 718 719 720 721 722
    context 'sorting' do
      context 'by project statistics' do
        %w(repository_size storage_size wiki_size packages_size).each do |order_by|
          context "sorting by #{order_by}" do
            before do
              ProjectStatistics.update_all(order_by => 100)
              project4.statistics.update_columns(order_by => 10)
              project.statistics.update_columns(order_by => 200)
            end
723

724 725
            context 'admin user' do
              let(:current_user) { admin }
726

727 728 729
              context "when sorting by #{order_by} ascendingly" do
                it 'returns a properly sorted list of projects' do
                  get api('/projects', current_user), params: { order_by: order_by, sort: :asc }
730

731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
                  expect(response).to have_gitlab_http_status(:ok)
                  expect(response).to include_pagination_headers
                  expect(json_response).to be_an Array
                  expect(json_response.first['id']).to eq(project4.id)
                end
              end

              context "when sorting by #{order_by} descendingly" do
                it 'returns a properly sorted list of projects' do
                  get api('/projects', current_user), params: { order_by: order_by, sort: :desc }

                  expect(response).to have_gitlab_http_status(:ok)
                  expect(response).to include_pagination_headers
                  expect(json_response).to be_an Array
                  expect(json_response.first['id']).to eq(project.id)
                end
747 748 749
              end
            end

750 751 752 753 754
            context 'non-admin user' do
              let(:current_user) { user }

              it 'returns projects ordered normally' do
                get api('/projects', current_user), params: { order_by: order_by }
755 756 757 758

                expect(response).to have_gitlab_http_status(:ok)
                expect(response).to include_pagination_headers
                expect(json_response).to be_an Array
759
                expect(json_response.map { |project| project['id'] }).to eq(user_projects.map(&:id).sort.reverse)
760 761 762
              end
            end
          end
763 764
        end
      end
765

766 767 768 769 770 771
      context 'by similarity', :aggregate_failures do
        let_it_be(:group_with_projects) { create(:group) }
        let_it_be(:project_1) { create(:project, name: 'Project', path: 'project', group: group_with_projects) }
        let_it_be(:project_2) { create(:project, name: 'Test Project', path: 'test-project', group: group_with_projects) }
        let_it_be(:project_3) { create(:project, name: 'Test', path: 'test', group: group_with_projects) }
        let_it_be(:project_4) { create(:project, :public, name: 'Test Public Project') }
772

773 774
        let(:current_user) { user }
        let(:params) { { order_by: 'similarity', search: 'test' } }
775

776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819
        subject { get api('/projects', current_user), params: params }

        before do
          group_with_projects.add_owner(current_user)
        end

        it 'returns non-public items based ordered by similarity' do
          subject

          expect(response).to have_gitlab_http_status(:ok)
          expect(response).to include_pagination_headers
          expect(json_response.length).to eq(2)

          project_names = json_response.map { |proj| proj['name'] }
          expect(project_names).to contain_exactly('Test', 'Test Project')
        end

        context 'when `search` parameter is not given' do
          let(:params) { { order_by: 'similarity' } }

          it 'returns items ordered by created_at descending' do
            subject

            expect(response).to have_gitlab_http_status(:ok)
            expect(response).to include_pagination_headers
            expect(json_response.length).to eq(8)

            project_names = json_response.map { |proj| proj['name'] }
            expect(project_names).to contain_exactly(project.name, project2.name, 'second_project', 'public_project', 'Project', 'Test Project', 'Test Public Project', 'Test')
          end
        end

        context 'when called anonymously' do
          let(:current_user) { nil }

          it 'returns items ordered by created_at descending' do
            subject

            expect(response).to have_gitlab_http_status(:ok)
            expect(response).to include_pagination_headers
            expect(json_response.length).to eq(1)

            project_names = json_response.map { |proj| proj['name'] }
            expect(project_names).to contain_exactly('Test Public Project')
820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851
          end
        end
      end
    end

    context 'filtering by repository_storage' do
      before do
        [project, project3].each { |proj| proj.update_columns(repository_storage: 'nfs-11') }
        # Since we don't actually have Gitaly configured with an nfs-11 storage, an error would be raised
        # when we present the projects in a response, as we ask Gitaly for stuff like default branch and Gitaly
        # is not configured for a nfs-11 storage. So we trick Rails into thinking the storage for these projects
        # is still default (in reality, it is).
        allow_any_instance_of(Project).to receive(:repository_storage).and_return('default')
      end

      context 'admin user' do
        it_behaves_like 'projects response' do
          let(:filter) { { repository_storage: 'nfs-11' } }
          let(:current_user) { admin }
          let(:projects) { [project, project3] }
        end
      end

      context 'non-admin user' do
        it_behaves_like 'projects response' do
          let(:filter) { { repository_storage: 'nfs-11' } }
          let(:current_user) { user }
          let(:projects) { [public_project, project, project2, project3] }
        end
      end
    end

852 853
    context 'with keyset pagination' do
      let(:current_user) { user }
854 855
      let(:first_project_id) { user_projects.map(&:id).min }
      let(:last_project_id) { user_projects.map(&:id).max }
856 857 858 859 860 861 862

      context 'headers and records' do
        let(:params) { { pagination: 'keyset', order_by: :id, sort: :asc, per_page: 1 } }

        it 'includes a pagination header with link to the next page' do
          get api('/projects', current_user), params: params

863 864
          expect(response.header).to include('Link')
          expect(response.header['Link']).to include('pagination=keyset')
865
          expect(response.header['Link']).to include("id_after=#{first_project_id}")
866 867 868 869 870
        end

        it 'contains only the first project with per_page = 1' do
          get api('/projects', current_user), params: params

871
          expect(response).to have_gitlab_http_status(:ok)
872
          expect(json_response).to be_an Array
873
          expect(json_response.map { |p| p['id'] }).to contain_exactly(first_project_id)
874 875
        end

876
        it 'still includes a link if the end has reached and there is no more data after this page' do
877 878
          get api('/projects', current_user), params: params.merge(id_after: project2.id)

879 880 881
          expect(response.header).to include('Link')
          expect(response.header['Link']).to include('pagination=keyset')
          expect(response.header['Link']).to include("id_after=#{project3.id}")
882 883 884 885 886
        end

        it 'does not include a next link when the page does not have any records' do
          get api('/projects', current_user), params: params.merge(id_after: Project.maximum(:id))

887
          expect(response.header).not_to include('Link')
888 889
        end

890 891 892
        it 'returns an empty array when the page does not have any records' do
          get api('/projects', current_user), params: params.merge(id_after: Project.maximum(:id))

893
          expect(response).to have_gitlab_http_status(:ok)
894 895 896
          expect(json_response).to eq([])
        end

897 898 899
        it 'responds with 501 if order_by is different from id' do
          get api('/projects', current_user), params: params.merge(order_by: :created_at)

900
          expect(response).to have_gitlab_http_status(:method_not_allowed)
901 902 903 904 905 906 907 908 909
        end
      end

      context 'with descending sorting' do
        let(:params) { { pagination: 'keyset', order_by: :id, sort: :desc, per_page: 1 } }

        it 'includes a pagination header with link to the next page' do
          get api('/projects', current_user), params: params

910 911
          expect(response.header).to include('Link')
          expect(response.header['Link']).to include('pagination=keyset')
912
          expect(response.header['Link']).to include("id_before=#{last_project_id}")
913 914 915 916 917
        end

        it 'contains only the last project with per_page = 1' do
          get api('/projects', current_user), params: params

918
          expect(response).to have_gitlab_http_status(:ok)
919
          expect(json_response).to be_an Array
920
          expect(json_response.map { |p| p['id'] }).to contain_exactly(last_project_id)
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935
        end
      end

      context 'retrieving the full relation' do
        let(:params) { { pagination: 'keyset', order_by: :id, sort: :desc, per_page: 2 } }

        it 'returns all projects' do
          url = '/projects'
          requests = 0
          ids = []

          while url && requests <= 5 # circuit breaker
            requests += 1
            get api(url, current_user), params: params

936
            link = response.header['Link']
937
            url = link&.match(%r{<[^>]+(/projects\?[^>]+)>; rel="next"}) do |match|
938 939 940
              match[1]
            end

941
            ids += Gitlab::Json.parse(response.body).map { |p| p['id'] }
942 943
          end

944
          expect(ids).to contain_exactly(*user_projects.map(&:id))
945 946 947
        end
      end
    end
948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972

    context 'with forked projects', :use_clean_rails_memory_store_caching do
      include ProjectForksHelper

      let_it_be(:admin) { create(:admin) }

      it 'avoids N+1 queries' do
        get api('/projects', admin)

        base_project = create(:project, :public, namespace: admin.namespace)

        fork_project1 = fork_project(base_project, admin, namespace: create(:user).namespace)
        fork_project2 = fork_project(fork_project1, admin, namespace: create(:user).namespace)

        control = ActiveRecord::QueryRecorder.new do
          get api('/projects', admin)
        end

        fork_project(fork_project2, admin, namespace: create(:user).namespace)

        expect do
          get api('/projects', admin)
        end.not_to exceed_query_limit(control.count)
      end
    end
973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995

    context 'when service desk is enabled', :use_clean_rails_memory_store_caching do
      let_it_be(:admin) { create(:admin) }

      it 'avoids N+1 queries' do
        allow(Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(true)
        allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)

        get api('/projects', admin)

        create(:project, :public, :service_desk_enabled, namespace: admin.namespace)

        control = ActiveRecord::QueryRecorder.new do
          get api('/projects', admin)
        end

        create_list(:project, 2, :public, :service_desk_enabled, namespace: admin.namespace)

        expect do
          get api('/projects', admin)
        end.not_to exceed_query_limit(control.count)
      end
    end
996 997
  end

998 999
  describe 'POST /projects' do
    context 'maximum number of projects reached' do
1000
      it 'does not create new project and respond with 403' do
1001
        allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0)
blackst0ne's avatar
blackst0ne committed
1002
        expect { post api('/projects', user2), params: { name: 'foo' } }
1003
          .to change {Project.count}.by(0)
1004
        expect(response).to have_gitlab_http_status(:forbidden)
1005 1006 1007
      end
    end

1008
    it 'creates new project without path but with name and returns 201' do
blackst0ne's avatar
blackst0ne committed
1009
      expect { post api('/projects', user), params: { name: 'Foo Project' } }
1010
        .to change { Project.count }.by(1)
1011
      expect(response).to have_gitlab_http_status(:created)
1012

1013
      project = Project.last
1014 1015 1016 1017 1018 1019

      expect(project.name).to eq('Foo Project')
      expect(project.path).to eq('foo-project')
    end

    it 'creates new project without name but with path and returns 201' do
blackst0ne's avatar
blackst0ne committed
1020
      expect { post api('/projects', user), params: { path: 'foo_project' } }
1021
        .to change { Project.count }.by(1)
1022
      expect(response).to have_gitlab_http_status(:created)
1023

1024
      project = Project.last
1025 1026 1027 1028 1029

      expect(project.name).to eq('foo_project')
      expect(project.path).to eq('foo_project')
    end

1030
    it 'creates new project with name and path and returns 201' do
blackst0ne's avatar
blackst0ne committed
1031
      expect { post api('/projects', user), params: { path: 'path-project-Foo', name: 'Foo Project' } }
1032
        .to change { Project.count }.by(1)
1033
      expect(response).to have_gitlab_http_status(:created)
1034

1035
      project = Project.last
1036 1037

      expect(project.name).to eq('Foo Project')
1038
      expect(project.path).to eq('path-project-Foo')
1039 1040
    end

1041 1042 1043 1044
    it_behaves_like 'create project with default branch parameter' do
      let(:request) { post api('/projects', user), params: params }
    end

1045
    it 'creates last project before reaching project limit' do
1046
      allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1)
blackst0ne's avatar
blackst0ne committed
1047
      post api('/projects', user2), params: { name: 'foo' }
1048
      expect(response).to have_gitlab_http_status(:created)
1049 1050
    end

1051
    it 'does not create new project without name or path and returns 400' do
1052
      expect { post api('/projects', user) }.not_to change { Project.count }
1053
      expect(response).to have_gitlab_http_status(:bad_request)
1054
    end
Alex Denisov's avatar
Alex Denisov committed
1055

1056
    it "assigns attributes to project", :aggregate_failures do
1057
      project = attributes_for(:project, {
1058
        path: 'camelCasePath',
1059
        issues_enabled: false,
winniehell's avatar
winniehell committed
1060
        jobs_enabled: false,
1061
        merge_requests_enabled: false,
1062
        forking_access_level: 'disabled',
1063
        analytics_access_level: 'disabled',
1064
        wiki_enabled: false,
1065
        resolve_outdated_diff_discussions: false,
1066
        remove_source_branch_after_merge: true,
1067
        autoclose_referenced_issues: true,
1068 1069
        only_allow_merge_if_pipeline_succeeds: true,
        allow_merge_on_skipped_pipeline: true,
1070
        request_access_enabled: true,
1071
        only_allow_merge_if_all_discussions_are_resolved: false,
1072
        ci_config_path: 'a/custom/path',
1073 1074
        merge_method: 'ff',
        squash_option: 'always'
1075 1076
      }).tap do |attrs|
        attrs[:operations_access_level] = 'disabled'
1077
        attrs[:analytics_access_level] = 'disabled'
1078
        attrs[:container_registry_access_level] = 'private'
1079
      end
Alex Denisov's avatar
Alex Denisov committed
1080

blackst0ne's avatar
blackst0ne committed
1081
      post api('/projects', user), params: project
Alex Denisov's avatar
Alex Denisov committed
1082

1083
      expect(response).to have_gitlab_http_status(:created)
winniehell's avatar
winniehell committed
1084

1085
      project.each_pair do |k, v|
1086 1087 1088 1089
        next if %i[
          has_external_issue_tracker has_external_wiki issues_enabled merge_requests_enabled wiki_enabled storage_version
          container_registry_access_level
        ].include?(k)
1090

1091
        expect(json_response[k.to_s]).to eq(v)
Alex Denisov's avatar
Alex Denisov committed
1092
      end
1093 1094 1095 1096 1097 1098

      # Check feature permissions attributes
      project = Project.find_by_path(project[:path])
      expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED)
      expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::DISABLED)
      expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED)
1099
      expect(project.operations_access_level).to eq(ProjectFeature::DISABLED)
1100
      expect(project.project_feature.analytics_access_level).to eq(ProjectFeature::DISABLED)
1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112
      expect(project.project_feature.container_registry_access_level).to eq(ProjectFeature::PRIVATE)
    end

    it 'assigns container_registry_enabled to project', :aggregate_failures do
      project = attributes_for(:project, { container_registry_enabled: true })

      post api('/projects', user), params: project

      expect(response).to have_gitlab_http_status(:created)
      expect(json_response['container_registry_enabled']).to eq(true)
      expect(json_response['container_registry_access_level']).to eq('enabled')
      expect(Project.find_by(path: project[:path]).container_registry_access_level).to eq(ProjectFeature::ENABLED)
1113
    end
1114

1115 1116 1117 1118 1119 1120 1121 1122 1123 1124
    it 'assigns container_registry_enabled to project' do
      project = attributes_for(:project, { container_registry_enabled: true })

      post api('/projects', user), params: project

      expect(response).to have_gitlab_http_status(:created)
      expect(json_response['container_registry_enabled']).to eq(true)
      expect(Project.find_by(path: project[:path]).container_registry_access_level).to eq(ProjectFeature::ENABLED)
    end

1125 1126 1127 1128
    it 'creates a project using a template' do
      expect { post api('/projects', user), params: { template_name: 'rails', name: 'rails-test' } }
        .to change { Project.count }.by(1)

1129
      expect(response).to have_gitlab_http_status(:created)
1130 1131 1132 1133 1134 1135

      project = Project.find(json_response['id'])
      expect(project).to be_saved
      expect(project.import_type).to eq('gitlab_project')
    end

1136 1137 1138 1139
    it 'returns 400 for an invalid template' do
      expect { post api('/projects', user), params: { template_name: 'unknown', name: 'rails-test' } }
        .not_to change { Project.count }

1140
      expect(response).to have_gitlab_http_status(:bad_request)
1141 1142 1143
      expect(json_response['message']['template_name']).to eq(["'unknown' is unknown or invalid"])
    end

1144 1145 1146 1147 1148
    it 'disallows creating a project with an import_url and template' do
      project_params = { import_url: 'http://example.com', template_name: 'rails', name: 'rails-test' }
      expect { post api('/projects', user), params: project_params }
        .not_to change {  Project.count }

1149
      expect(response).to have_gitlab_http_status(:bad_request)
1150 1151
    end

1152
    it 'sets a project as public' do
1153
      project = attributes_for(:project, visibility: 'public')
1154

blackst0ne's avatar
blackst0ne committed
1155
      post api('/projects', user), params: project
1156 1157

      expect(json_response['visibility']).to eq('public')
1158 1159
    end

1160
    it 'sets a project as internal' do
1161 1162
      project = attributes_for(:project, visibility: 'internal')

blackst0ne's avatar
blackst0ne committed
1163
      post api('/projects', user), params: project
1164 1165

      expect(json_response['visibility']).to eq('internal')
1166 1167
    end

1168
    it 'sets a project as private' do
1169 1170
      project = attributes_for(:project, visibility: 'private')

blackst0ne's avatar
blackst0ne committed
1171
      post api('/projects', user), params: project
1172 1173

      expect(json_response['visibility']).to eq('private')
1174 1175
    end

Steve's avatar
Steve committed
1176 1177 1178
    it 'creates a new project initialized with a README.md' do
      project = attributes_for(:project, initialize_with_readme: 1, name: 'somewhere')

blackst0ne's avatar
blackst0ne committed
1179
      post api('/projects', user), params: project
Steve's avatar
Steve committed
1180

1181
      expect(json_response['readme_url']).to eql("#{Gitlab.config.gitlab.url}/#{json_response['namespace']['full_path']}/somewhere/-/blob/master/README.md")
Steve's avatar
Steve committed
1182 1183
    end

1184
    it 'sets tag list to a project (deprecated)' do
1185 1186
      project = attributes_for(:project, tag_list: %w[tagFirst tagSecond])

blackst0ne's avatar
blackst0ne committed
1187
      post api('/projects', user), params: project
1188

1189
      expect(json_response['topics']).to eq(%w[tagFirst tagSecond])
1190 1191
    end

1192 1193 1194 1195 1196
    it 'sets topics to a project' do
      project = attributes_for(:project, topics: %w[topic1 topics2])

      post api('/projects', user), params: project

1197
      expect(json_response['topics']).to eq(%w[topic1 topics2])
1198 1199
    end

1200
    it 'uploads avatar for project a project' do
1201
      project = attributes_for(:project, avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif'))
1202

blackst0ne's avatar
blackst0ne committed
1203
      post api('/projects', user), params: project
1204 1205

      project_id = json_response['id']
1206
      expect(json_response['avatar_url']).to eq("http://localhost/uploads/-/system/project/avatar/#{project_id}/banana_sample.gif")
1207
    end
1208

1209
    it 'sets a project as not allowing outdated diff discussions to automatically resolve' do
Sean McGivern's avatar
Sean McGivern committed
1210 1211
      project = attributes_for(:project, resolve_outdated_diff_discussions: false)

blackst0ne's avatar
blackst0ne committed
1212
      post api('/projects', user), params: project
Sean McGivern's avatar
Sean McGivern committed
1213

1214
      expect(json_response['resolve_outdated_diff_discussions']).to be_falsey
1215 1216
    end

1217
    it 'sets a project as allowing outdated diff discussions to automatically resolve' do
Sean McGivern's avatar
Sean McGivern committed
1218 1219
      project = attributes_for(:project, resolve_outdated_diff_discussions: true)

blackst0ne's avatar
blackst0ne committed
1220
      post api('/projects', user), params: project
Sean McGivern's avatar
Sean McGivern committed
1221

1222
      expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
1223 1224
    end

1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240
    it 'sets a project as not removing source branches' do
      project = attributes_for(:project, remove_source_branch_after_merge: false)

      post api('/projects', user), params: project

      expect(json_response['remove_source_branch_after_merge']).to be_falsey
    end

    it 'sets a project as removing source branches' do
      project = attributes_for(:project, remove_source_branch_after_merge: true)

      post api('/projects', user), params: project

      expect(json_response['remove_source_branch_after_merge']).to be_truthy
    end

1241
    it 'sets a project as allowing merge even if build fails' do
Sean McGivern's avatar
Sean McGivern committed
1242 1243
      project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: false)

blackst0ne's avatar
blackst0ne committed
1244
      post api('/projects', user), params: project
Sean McGivern's avatar
Sean McGivern committed
1245

1246
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
1247 1248
    end

1249
    it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do
Sean McGivern's avatar
Sean McGivern committed
1250 1251
      project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: true)

blackst0ne's avatar
blackst0ne committed
1252
      post api('/projects', user), params: project
Sean McGivern's avatar
Sean McGivern committed
1253

1254
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
1255 1256
    end

1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272
    it 'sets a project as not allowing merge when pipeline is skipped' do
      project_params = attributes_for(:project, allow_merge_on_skipped_pipeline: false)

      post api('/projects', user), params: project_params

      expect(json_response['allow_merge_on_skipped_pipeline']).to be_falsey
    end

    it 'sets a project as allowing merge when pipeline is skipped' do
      project_params = attributes_for(:project, allow_merge_on_skipped_pipeline: true)

      post api('/projects', user), params: project_params

      expect(json_response['allow_merge_on_skipped_pipeline']).to be_truthy
    end

1273
    it 'sets a project as allowing merge even if discussions are unresolved' do
Sean McGivern's avatar
Sean McGivern committed
1274
      project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: false)
1275

blackst0ne's avatar
blackst0ne committed
1276
      post api('/projects', user), params: project
1277 1278

      expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey
1279 1280
    end

1281 1282 1283
    it 'sets a project as allowing merge if only_allow_merge_if_all_discussions_are_resolved is nil' do
      project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: nil)

blackst0ne's avatar
blackst0ne committed
1284
      post api('/projects', user), params: project
1285 1286 1287 1288

      expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey
    end

1289
    it 'sets a project as allowing merge only if all discussions are resolved' do
Sean McGivern's avatar
Sean McGivern committed
1290
      project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: true)
1291

blackst0ne's avatar
blackst0ne committed
1292
      post api('/projects', user), params: project
1293 1294 1295 1296

      expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
    end

1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312
    it 'sets a project as enabling auto close referenced issues' do
      project = attributes_for(:project, autoclose_referenced_issues: true)

      post api('/projects', user), params: project

      expect(json_response['autoclose_referenced_issues']).to be_truthy
    end

    it 'sets a project as disabling auto close referenced issues' do
      project = attributes_for(:project, autoclose_referenced_issues: false)

      post api('/projects', user), params: project

      expect(json_response['autoclose_referenced_issues']).to be_falsey
    end

1313 1314 1315
    it 'sets the merge method of a project to rebase merge' do
      project = attributes_for(:project, merge_method: 'rebase_merge')

blackst0ne's avatar
blackst0ne committed
1316
      post api('/projects', user), params: project
1317 1318 1319 1320 1321 1322 1323

      expect(json_response['merge_method']).to eq('rebase_merge')
    end

    it 'rejects invalid values for merge_method' do
      project = attributes_for(:project, merge_method: 'totally_not_valid_method')

blackst0ne's avatar
blackst0ne committed
1324
      post api('/projects', user), params: project
1325

1326
      expect(response).to have_gitlab_http_status(:bad_request)
1327 1328
    end

1329
    it 'ignores import_url when it is nil' do
Sean McGivern's avatar
Sean McGivern committed
1330
      project = attributes_for(:project, import_url: nil)
1331

blackst0ne's avatar
blackst0ne committed
1332
      post api('/projects', user), params: project
1333

1334
      expect(response).to have_gitlab_http_status(:created)
1335 1336
    end

1337
    context 'when a visibility level is restricted' do
1338
      let(:project_param) { attributes_for(:project, visibility: 'public') }
1339

1340
      before do
1341
        stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
1342 1343
      end

1344
      it 'does not allow a non-admin to use a restricted visibility level' do
blackst0ne's avatar
blackst0ne committed
1345
        post api('/projects', user), params: project_param
Felipe Artur's avatar
Felipe Artur committed
1346

1347
        expect(response).to have_gitlab_http_status(:bad_request)
1348 1349 1350 1351 1352
        expect(json_response['message']['visibility_level'].first).to(
          match('restricted by your GitLab administrator')
        )
      end

1353
      it 'allows an admin to override restricted visibility settings' do
blackst0ne's avatar
blackst0ne committed
1354
        post api('/projects', admin), params: project_param
1355

1356
        expect(json_response['visibility']).to eq('public')
1357 1358
      end
    end
1359 1360
  end

vanadium23's avatar
vanadium23 committed
1361
  describe 'GET /users/:user_id/projects/' do
1362
    let!(:public_project) { create(:project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }
vanadium23's avatar
vanadium23 committed
1363 1364

    it 'returns error when user not found' do
1365
      get api("/users/#{non_existing_record_id}/projects/")
vanadium23's avatar
vanadium23 committed
1366

1367
      expect(response).to have_gitlab_http_status(:not_found)
vanadium23's avatar
vanadium23 committed
1368 1369 1370
      expect(json_response['message']).to eq('404 User Not Found')
    end

1371
    it 'returns projects filtered by user id' do
vanadium23's avatar
vanadium23 committed
1372 1373
      get api("/users/#{user4.id}/projects/", user)

1374
      expect(response).to have_gitlab_http_status(:ok)
vanadium23's avatar
vanadium23 committed
1375 1376 1377 1378
      expect(response).to include_pagination_headers
      expect(json_response).to be_an Array
      expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id)
    end
1379

1380 1381 1382 1383 1384 1385 1386 1387
    it 'includes container_registry_access_level', :aggregate_failures do
      get api("/users/#{user4.id}/projects/", user)

      expect(response).to have_gitlab_http_status(:ok)
      expect(json_response).to be_an Array
      expect(json_response.first.keys).to include('container_registry_access_level')
    end

1388 1389 1390 1391 1392 1393
    context 'and using id_after' do
      let!(:another_public_project) { create(:project, :public, name: 'another_public_project', creator_id: user4.id, namespace: user4.namespace) }

      it 'only returns projects with id_after filter given' do
        get api("/users/#{user4.id}/projects?id_after=#{public_project.id}", user)

1394
        expect(response).to have_gitlab_http_status(:ok)
1395 1396 1397 1398 1399 1400 1401 1402
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response.map { |project| project['id'] }).to contain_exactly(another_public_project.id)
      end

      it 'returns both projects without a id_after filter' do
        get api("/users/#{user4.id}/projects", user)

1403
        expect(response).to have_gitlab_http_status(:ok)
1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id, another_public_project.id)
      end
    end

    context 'and using id_before' do
      let!(:another_public_project) { create(:project, :public, name: 'another_public_project', creator_id: user4.id, namespace: user4.namespace) }

      it 'only returns projects with id_before filter given' do
        get api("/users/#{user4.id}/projects?id_before=#{another_public_project.id}", user)

1416
        expect(response).to have_gitlab_http_status(:ok)
1417 1418 1419 1420 1421 1422 1423 1424
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id)
      end

      it 'returns both projects without a id_before filter' do
        get api("/users/#{user4.id}/projects", user)

1425
        expect(response).to have_gitlab_http_status(:ok)
1426 1427 1428 1429 1430 1431
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id, another_public_project.id)
      end
    end

1432 1433 1434 1435 1436 1437
    context 'and using both id_before and id_after' do
      let!(:more_projects) { create_list(:project, 5, :public, creator_id: user4.id, namespace: user4.namespace) }

      it 'only returns projects with id matching the range' do
        get api("/users/#{user4.id}/projects?id_after=#{more_projects.first.id}&id_before=#{more_projects.last.id}", user)

1438
        expect(response).to have_gitlab_http_status(:ok)
1439 1440 1441 1442 1443 1444
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response.map { |project| project['id'] }).to contain_exactly(*more_projects[1..-2].map(&:id))
      end
    end

1445 1446 1447
    it 'returns projects filtered by username' do
      get api("/users/#{user4.username}/projects/", user)

1448
      expect(response).to have_gitlab_http_status(:ok)
vanadium23's avatar
vanadium23 committed
1449 1450 1451 1452
      expect(response).to include_pagination_headers
      expect(json_response).to be_an Array
      expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id)
    end
1453

1454
    it 'returns projects filtered by minimal access level' do
1455 1456 1457 1458 1459
      private_project1 = create(:project, :private, name: 'private_project1', creator_id: user4.id, namespace: user4.namespace)
      private_project2 = create(:project, :private, name: 'private_project2', creator_id: user4.id, namespace: user4.namespace)
      private_project1.add_developer(user2)
      private_project2.add_reporter(user2)

blackst0ne's avatar
blackst0ne committed
1460
      get api("/users/#{user4.id}/projects/", user2), params: { min_access_level: 30 }
1461

1462
      expect(response).to have_gitlab_http_status(:ok)
1463 1464 1465 1466
      expect(response).to include_pagination_headers
      expect(json_response).to be_an Array
      expect(json_response.map { |project| project['id'] }).to contain_exactly(private_project1.id)
    end
1467 1468 1469 1470 1471 1472 1473

    context 'and using the programming language filter' do
      include_context 'with language detection'

      it 'filters case-insensitively by programming language' do
        get api('/projects', user), params: { with_programming_language: 'ruby' }

1474
        expect(response).to have_gitlab_http_status(:ok)
1475 1476 1477 1478 1479
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id)
      end
    end
vanadium23's avatar
vanadium23 committed
1480 1481
  end

1482 1483
  describe 'GET /users/:user_id/starred_projects/' do
    before do
1484
      user3.update!(starred_projects: [project, project2, project3])
1485
      user3.reload
1486 1487 1488
    end

    it 'returns error when user not found' do
1489
      get api("/users/#{non_existing_record_id}/starred_projects/")
1490

1491
      expect(response).to have_gitlab_http_status(:not_found)
1492 1493 1494
      expect(json_response['message']).to eq('404 User Not Found')
    end

1495 1496 1497
    context 'with a public profile' do
      it 'returns projects filtered by user' do
        get api("/users/#{user3.id}/starred_projects/", user)
1498

1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534
        expect(response).to have_gitlab_http_status(:ok)
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response.map { |project| project['id'] })
          .to contain_exactly(project.id, project2.id, project3.id)
      end
    end

    context 'with a private profile' do
      before do
        user3.update!(private_profile: true)
        user3.reload
      end

      context 'user does not have access to view the private profile' do
        it 'returns no projects' do
          get api("/users/#{user3.id}/starred_projects/", user)

          expect(response).to have_gitlab_http_status(:ok)
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response).to be_empty
        end
      end

      context 'user has access to view the private profile' do
        it 'returns projects filtered by user' do
          get api("/users/#{user3.id}/starred_projects/", admin)

          expect(response).to have_gitlab_http_status(:ok)
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.map { |project| project['id'] })
            .to contain_exactly(project.id, project2.id, project3.id)
        end
      end
1535 1536 1537
    end
  end

1538
  describe 'POST /projects/user/:id' do
1539
    it 'creates new project without path but with name and return 201' do
blackst0ne's avatar
blackst0ne committed
1540
      expect { post api("/projects/user/#{user.id}", admin), params: { name: 'Foo Project' } }.to change { Project.count }.by(1)
1541
      expect(response).to have_gitlab_http_status(:created)
1542

1543
      project = Project.find(json_response['id'])
1544 1545 1546 1547 1548 1549

      expect(project.name).to eq('Foo Project')
      expect(project.path).to eq('foo-project')
    end

    it 'creates new project with name and path and returns 201' do
blackst0ne's avatar
blackst0ne committed
1550
      expect { post api("/projects/user/#{user.id}", admin), params: { path: 'path-project-Foo', name: 'Foo Project' } }
1551
        .to change { Project.count }.by(1)
1552
      expect(response).to have_gitlab_http_status(:created)
1553

1554
      project = Project.find(json_response['id'])
1555 1556 1557

      expect(project.name).to eq('Foo Project')
      expect(project.path).to eq('path-project-Foo')
Angus MacArthur's avatar
Angus MacArthur committed
1558 1559
    end

1560 1561 1562 1563
    it_behaves_like 'create project with default branch parameter' do
      let(:request) { post api("/projects/user/#{user.id}", admin), params: params }
    end

1564
    it 'responds with 400 on failure and not project' do
1565 1566
      expect { post api("/projects/user/#{user.id}", admin) }
        .not_to change { Project.count }
1567

1568
      expect(response).to have_gitlab_http_status(:bad_request)
1569
      expect(json_response['error']).to eq('name is missing')
Angus MacArthur's avatar
Angus MacArthur committed
1570 1571
    end

1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583
    it 'sets container_registry_enabled' do
      project = attributes_for(:project).tap do |attrs|
        attrs[:container_registry_enabled] = true
      end

      post api("/projects/user/#{user.id}", admin), params: project

      expect(response).to have_gitlab_http_status(:created)
      expect(json_response['container_registry_enabled']).to eq(true)
      expect(Project.find_by(path: project[:path]).container_registry_access_level).to eq(ProjectFeature::ENABLED)
    end

1584
    it 'assigns attributes to project' do
Angus MacArthur's avatar
Angus MacArthur committed
1585
      project = attributes_for(:project, {
1586 1587
        issues_enabled: false,
        merge_requests_enabled: false,
1588
        wiki_enabled: false,
1589 1590
        request_access_enabled: true,
        jobs_enabled: true
Angus MacArthur's avatar
Angus MacArthur committed
1591 1592
      })

blackst0ne's avatar
blackst0ne committed
1593
      post api("/projects/user/#{user.id}", admin), params: project
Angus MacArthur's avatar
Angus MacArthur committed
1594

1595
      expect(response).to have_gitlab_http_status(:created)
1596

1597
      project.each_pair do |k, v|
1598
        next if %i[has_external_issue_tracker has_external_wiki path storage_version].include?(k)
1599

1600
        expect(json_response[k.to_s]).to eq(v)
Angus MacArthur's avatar
Angus MacArthur committed
1601 1602
      end
    end
1603

1604
    it 'sets a project as public' do
1605
      project = attributes_for(:project, visibility: 'public')
1606

blackst0ne's avatar
blackst0ne committed
1607
      post api("/projects/user/#{user.id}", admin), params: project
1608

1609
      expect(response).to have_gitlab_http_status(:created)
1610
      expect(json_response['visibility']).to eq('public')
1611
    end
1612

1613
    it 'sets a project as internal' do
1614 1615
      project = attributes_for(:project, visibility: 'internal')

blackst0ne's avatar
blackst0ne committed
1616
      post api("/projects/user/#{user.id}", admin), params: project
1617

1618
      expect(response).to have_gitlab_http_status(:created)
1619
      expect(json_response['visibility']).to eq('internal')
1620 1621
    end

1622
    it 'sets a project as private' do
1623 1624
      project = attributes_for(:project, visibility: 'private')

blackst0ne's avatar
blackst0ne committed
1625
      post api("/projects/user/#{user.id}", admin), params: project
1626 1627

      expect(json_response['visibility']).to eq('private')
1628
    end
1629

1630
    it 'sets a project as not allowing outdated diff discussions to automatically resolve' do
Sean McGivern's avatar
Sean McGivern committed
1631
      project = attributes_for(:project, resolve_outdated_diff_discussions: false)
1632

blackst0ne's avatar
blackst0ne committed
1633
      post api("/projects/user/#{user.id}", admin), params: project
1634

1635
      expect(json_response['resolve_outdated_diff_discussions']).to be_falsey
1636 1637
    end

Sean McGivern's avatar
Sean McGivern committed
1638 1639
    it 'sets a project as allowing outdated diff discussions to automatically resolve' do
      project = attributes_for(:project, resolve_outdated_diff_discussions: true)
1640

blackst0ne's avatar
blackst0ne committed
1641
      post api("/projects/user/#{user.id}", admin), params: project
1642

1643
      expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
1644 1645
    end

1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661
    it 'sets a project as not removing source branches' do
      project = attributes_for(:project, remove_source_branch_after_merge: false)

      post api("/projects/user/#{user.id}", admin), params: project

      expect(json_response['remove_source_branch_after_merge']).to be_falsey
    end

    it 'sets a project as removing source branches' do
      project = attributes_for(:project, remove_source_branch_after_merge: true)

      post api("/projects/user/#{user.id}", admin), params: project

      expect(json_response['remove_source_branch_after_merge']).to be_truthy
    end

1662
    it 'sets a project as allowing merge even if build fails' do
Sean McGivern's avatar
Sean McGivern committed
1663
      project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: false)
1664

blackst0ne's avatar
blackst0ne committed
1665
      post api("/projects/user/#{user.id}", admin), params: project
1666

1667
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
1668 1669
    end

Sean McGivern's avatar
Sean McGivern committed
1670 1671
    it 'sets a project as allowing merge only if pipeline succeeds' do
      project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: true)
1672

blackst0ne's avatar
blackst0ne committed
1673
      post api("/projects/user/#{user.id}", admin), params: project
1674

1675
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
1676
    end
1677

1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693
    it 'sets a project as not allowing merge when pipeline is skipped' do
      project = attributes_for(:project, allow_merge_on_skipped_pipeline: false)

      post api("/projects/user/#{user.id}", admin), params: project

      expect(json_response['allow_merge_on_skipped_pipeline']).to be_falsey
    end

    it 'sets a project as allowing merge when pipeline is skipped' do
      project = attributes_for(:project, allow_merge_on_skipped_pipeline: true)

      post api("/projects/user/#{user.id}", admin), params: project

      expect(json_response['allow_merge_on_skipped_pipeline']).to be_truthy
    end

1694
    it 'sets a project as allowing merge even if discussions are unresolved' do
Sean McGivern's avatar
Sean McGivern committed
1695
      project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: false)
1696

blackst0ne's avatar
blackst0ne committed
1697
      post api("/projects/user/#{user.id}", admin), params: project
1698 1699

      expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_falsey
1700 1701
    end

1702
    it 'sets a project as allowing merge only if all discussions are resolved' do
Sean McGivern's avatar
Sean McGivern committed
1703
      project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: true)
1704

blackst0ne's avatar
blackst0ne committed
1705
      post api("/projects/user/#{user.id}", admin), params: project
1706 1707

      expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
1708
    end
1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761

    context 'container_registry_enabled' do
      using RSpec::Parameterized::TableSyntax

      where(:container_registry_enabled, :container_registry_access_level) do
        true  | ProjectFeature::ENABLED
        false | ProjectFeature::DISABLED
      end

      with_them do
        it 'setting container_registry_enabled also sets container_registry_access_level', :aggregate_failures do
          project_attributes = attributes_for(:project).tap do |attrs|
            attrs[:container_registry_enabled] = container_registry_enabled
          end

          post api("/projects/user/#{user.id}", admin), params: project_attributes

          project = Project.find_by(path: project_attributes[:path])
          expect(response).to have_gitlab_http_status(:created)
          expect(json_response['container_registry_access_level']).to eq(ProjectFeature.str_from_access_level(container_registry_access_level))
          expect(json_response['container_registry_enabled']).to eq(container_registry_enabled)
          expect(project.container_registry_access_level).to eq(container_registry_access_level)
          expect(project.container_registry_enabled).to eq(container_registry_enabled)
        end
      end
    end

    context 'container_registry_access_level' do
      using RSpec::Parameterized::TableSyntax

      where(:container_registry_access_level, :container_registry_enabled) do
        'enabled'  | true
        'private'  | true
        'disabled' | false
      end

      with_them do
        it 'setting container_registry_access_level also sets container_registry_enabled', :aggregate_failures do
          project_attributes = attributes_for(:project).tap do |attrs|
            attrs[:container_registry_access_level] = container_registry_access_level
          end

          post api("/projects/user/#{user.id}", admin), params: project_attributes

          project = Project.find_by(path: project_attributes[:path])
          expect(response).to have_gitlab_http_status(:created)
          expect(json_response['container_registry_access_level']).to eq(container_registry_access_level)
          expect(json_response['container_registry_enabled']).to eq(container_registry_enabled)
          expect(project.container_registry_access_level).to eq(ProjectFeature.access_level_from_str(container_registry_access_level))
          expect(project.container_registry_enabled).to eq(container_registry_enabled)
        end
      end
    end
Angus MacArthur's avatar
Angus MacArthur committed
1762 1763
  end

1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785
  describe "POST /projects/:id/uploads/authorize" do
    include WorkhorseHelpers

    let(:headers) { workhorse_internal_api_request_header.merge({ 'HTTP_GITLAB_WORKHORSE' => 1 }) }

    context 'with authorized user' do
      it "returns 200" do
        post api("/projects/#{project.id}/uploads/authorize", user), headers: headers

        expect(response).to have_gitlab_http_status(:ok)
        expect(json_response['MaximumSize']).to eq(project.max_attachment_size)
      end
    end

    context 'with unauthorized user' do
      it "returns 404" do
        post api("/projects/#{project.id}/uploads/authorize", user2), headers: headers

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

1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811
    context 'with exempted project' do
      before do
        stub_env('GITLAB_UPLOAD_API_ALLOWLIST', project.id)
      end

      it "returns 200" do
        post api("/projects/#{project.id}/uploads/authorize", user), headers: headers

        expect(response).to have_gitlab_http_status(:ok)
        expect(json_response['MaximumSize']).to eq(1.gigabyte)
      end
    end

    context 'with upload size enforcement disabled' do
      before do
        stub_feature_flags(enforce_max_attachment_size_upload_api: false)
      end

      it "returns 200" do
        post api("/projects/#{project.id}/uploads/authorize", user), headers: headers

        expect(response).to have_gitlab_http_status(:ok)
        expect(json_response['MaximumSize']).to eq(1.gigabyte)
      end
    end

1812 1813 1814 1815 1816 1817 1818 1819 1820
    context 'with no Workhorse headers' do
      it "returns 403" do
        post api("/projects/#{project.id}/uploads/authorize", user)

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

1821
  describe "POST /projects/:id/uploads" do
1822 1823
    let(:file) { fixture_file_upload("spec/fixtures/dk.png", "image/png") }

1824 1825 1826
    before do
      project
    end
1827 1828

    it "uploads the file and returns its info" do
1829 1830 1831 1832
      expect_next_instance_of(UploadService) do |instance|
        expect(instance).to receive(:override_max_attachment_size=).with(project.max_attachment_size).and_call_original
      end

1833
      post api("/projects/#{project.id}/uploads", user), params: { file: file }
1834

1835
      expect(response).to have_gitlab_http_status(:created)
1836 1837 1838
      expect(json_response['alt']).to eq("dk")
      expect(json_response['url']).to start_with("/uploads/")
      expect(json_response['url']).to end_with("/dk.png")
1839
      expect(json_response['full_path']).to start_with("/#{project.namespace.path}/#{project.path}/uploads")
1840
    end
1841

1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858
    it "does not leave the temporary file in place after uploading, even when the tempfile reaper does not run" do
      tempfile = Tempfile.new('foo')
      path = tempfile.path

      allow_any_instance_of(Rack::TempfileReaper).to receive(:call) do |instance, env|
        instance.instance_variable_get(:@app).call(env)
      end

      expect(path).not_to be(nil)
      expect(Rack::Multipart::Parser::TEMPFILE_FACTORY).to receive(:call).and_return(tempfile)

      post api("/projects/#{project.id}/uploads", user), params: { file: fixture_file_upload("spec/fixtures/dk.png", "image/png") }

      expect(tempfile.path).to be(nil)
      expect(File.exist?(path)).to be(false)
    end

1859
    shared_examples 'capped upload attachments' do |upload_allowed|
1860 1861 1862 1863 1864
      it "limits the upload to 1 GB" do
        expect_next_instance_of(UploadService) do |instance|
          expect(instance).to receive(:override_max_attachment_size=).with(1.gigabyte).and_call_original
        end

1865
        post api("/projects/#{project.id}/uploads", user), params: { file: file }
1866 1867 1868

        expect(response).to have_gitlab_http_status(:created)
      end
1869 1870 1871 1872 1873 1874 1875 1876 1877 1878

      it "logs a warning if file exceeds attachment size" do
        allow(Gitlab::CurrentSettings).to receive(:max_attachment_size).and_return(0)

        expect(Gitlab::AppLogger).to receive(:info).with(
          hash_including(message: 'File exceeds maximum size', upload_allowed: upload_allowed))
            .and_call_original

        post api("/projects/#{project.id}/uploads", user), params: { file: file }
      end
1879 1880 1881 1882 1883 1884 1885
    end

    context 'with exempted project' do
      before do
        stub_env('GITLAB_UPLOAD_API_ALLOWLIST', project.id)
      end

1886
      it_behaves_like 'capped upload attachments', true
1887 1888 1889 1890 1891 1892 1893
    end

    context 'with upload size enforcement disabled' do
      before do
        stub_feature_flags(enforce_max_attachment_size_upload_api: false)
      end

1894
      it_behaves_like 'capped upload attachments', false
1895
    end
1896 1897
  end

1898 1899 1900 1901
  describe "GET /projects/:id/groups" do
    let_it_be(:root_group) { create(:group, :public, name: 'root group') }
    let_it_be(:project_group) { create(:group, :public, parent: root_group, name: 'project group') }
    let_it_be(:shared_group_with_dev_access) { create(:group, :private, parent: root_group, name: 'shared group') }
1902
    let_it_be(:shared_group_with_reporter_access) { create(:group, :public) }
1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983
    let_it_be(:private_project) { create(:project, :private, group: project_group) }
    let_it_be(:public_project) { create(:project, :public, group: project_group) }

    before_all do
      create(:project_group_link, :developer, group: shared_group_with_dev_access, project: private_project)
      create(:project_group_link, :reporter, group: shared_group_with_reporter_access, project: private_project)
    end

    shared_examples_for 'successful groups response' do
      it 'returns an array of groups' do
        request

        aggregate_failures do
          expect(response).to have_gitlab_http_status(:ok)
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.map { |g| g['name'] }).to match_array(expected_groups.map(&:name))
        end
      end
    end

    context 'when unauthenticated' do
      it 'does not return groups for private projects' do
        get api("/projects/#{private_project.id}/groups")

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

      context 'for public projects' do
        let(:request) { get api("/projects/#{public_project.id}/groups") }

        it_behaves_like 'successful groups response' do
          let(:expected_groups) { [root_group, project_group] }
        end
      end
    end

    context 'when authenticated as user' do
      context 'when user does not have access to the project' do
        it 'does not return groups' do
          get api("/projects/#{private_project.id}/groups", user)

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

      context 'when user has access to the project' do
        let(:request) { get api("/projects/#{private_project.id}/groups", user), params: params }
        let(:params) { {} }

        before do
          private_project.add_developer(user)
        end

        it_behaves_like 'successful groups response' do
          let(:expected_groups) { [root_group, project_group] }
        end

        context 'when search by root group name' do
          let(:params) { { search: 'root' } }

          it_behaves_like 'successful groups response' do
            let(:expected_groups) { [root_group] }
          end
        end

        context 'with_shared option is on' do
          let(:params) { { with_shared: true } }

          it_behaves_like 'successful groups response' do
            let(:expected_groups) { [root_group, project_group, shared_group_with_dev_access, shared_group_with_reporter_access] }
          end

          context 'when shared_min_access_level is set' do
            let(:params) { super().merge(shared_min_access_level: Gitlab::Access::DEVELOPER) }

            it_behaves_like 'successful groups response' do
              let(:expected_groups) { [root_group, project_group, shared_group_with_dev_access] }
            end
          end

1984 1985 1986 1987 1988 1989 1990 1991
          context 'when shared_visible_only is on' do
            let(:params) { super().merge(shared_visible_only: true) }

            it_behaves_like 'successful groups response' do
              let(:expected_groups) { [root_group, project_group, shared_group_with_reporter_access] }
            end
          end

1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019
          context 'when search by shared group name' do
            let(:params) { super().merge(search: 'shared') }

            it_behaves_like 'successful groups response' do
              let(:expected_groups) { [shared_group_with_dev_access] }
            end
          end

          context 'when skip_groups is set' do
            let(:params) { super().merge(skip_groups: [shared_group_with_dev_access.id, root_group.id]) }

            it_behaves_like 'successful groups response' do
              let(:expected_groups) { [shared_group_with_reporter_access, project_group] }
            end
          end
        end
      end
    end

    context 'when authenticated as admin' do
      let(:request) { get api("/projects/#{private_project.id}/groups", admin) }

      it_behaves_like 'successful groups response' do
        let(:expected_groups) { [root_group, project_group] }
      end
    end
  end

2020
  describe 'GET /projects/:id' do
2021
    context 'when unauthenticated' do
2022 2023 2024 2025 2026
      it 'does not return private projects' do
        private_project = create(:project, :private)

        get api("/projects/#{private_project.id}")

2027
        expect(response).to have_gitlab_http_status(:not_found)
2028 2029 2030 2031
      end

      it 'returns public projects' do
        public_project = create(:project, :repository, :public)
2032

2033
        get api("/projects/#{public_project.id}")
2034

2035
        expect(response).to have_gitlab_http_status(:ok)
2036 2037
        expect(json_response['id']).to eq(public_project.id)
        expect(json_response['description']).to eq(public_project.description)
2038
        expect(json_response['default_branch']).to eq(public_project.default_branch)
2039
        expect(json_response['ci_config_path']).to eq(public_project.ci_config_path)
2040 2041
        expect(json_response.keys).not_to include('permissions')
      end
2042

2043 2044 2045 2046 2047 2048 2049
      context 'the project is a public fork' do
        it 'hides details of a public fork parent' do
          public_project = create(:project, :repository, :public)
          fork = fork_project(public_project)

          get api("/projects/#{fork.id}")

2050
          expect(response).to have_gitlab_http_status(:ok)
2051 2052 2053 2054
          expect(json_response['forked_from_project']).to be_nil
        end
      end

2055 2056 2057 2058 2059 2060 2061
      context 'and the project has a private repository' do
        let(:project) { create(:project, :repository, :public, :repository_private) }
        let(:protected_attributes) { %w(default_branch ci_config_path) }

        it 'hides protected attributes of private repositories if user is not a member' do
          get api("/projects/#{project.id}", user)

2062
          expect(response).to have_gitlab_http_status(:ok)
2063 2064 2065 2066 2067 2068 2069 2070 2071 2072
          protected_attributes.each do |attribute|
            expect(json_response.keys).not_to include(attribute)
          end
        end

        it 'exposes protected attributes of private repositories if user is a member' do
          project.add_developer(user)

          get api("/projects/#{project.id}", user)

2073
          expect(response).to have_gitlab_http_status(:ok)
2074 2075 2076 2077 2078
          protected_attributes.each do |attribute|
            expect(json_response.keys).to include(attribute)
          end
        end
      end
2079
    end
2080

2081
    context 'when authenticated as an admin' do
2082 2083 2084 2085
      before do
        stub_container_registry_config(enabled: true, host_port: 'registry.example.org:5000')
      end

2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108
      let(:project_attributes_file) { 'spec/requests/api/project_attributes.yml' }
      let(:project_attributes) { YAML.load_file(project_attributes_file) }

      let(:expected_keys) do
        keys = project_attributes.map do |relation, relation_config|
          begin
            actual_keys = project.send(relation).attributes.keys
          rescue NoMethodError
            actual_keys = ["#{relation} is nil"]
          end
          unexposed_attributes = relation_config['unexposed_attributes'] || []
          remapped_attributes = relation_config['remapped_attributes'] || {}
          computed_attributes = relation_config['computed_attributes'] || []
          actual_keys - unexposed_attributes - remapped_attributes.keys + remapped_attributes.values + computed_attributes
        end.flatten

        unless Gitlab.ee?
          keys -= %w[
            approvals_before_merge
            compliance_frameworks
            mirror
            requirements_enabled
            security_and_compliance_enabled
2109 2110
            issues_template
            merge_requests_template
2111 2112 2113 2114 2115 2116
          ]
        end

        keys
      end

2117
      it 'returns a project by id', :aggregate_failures do
2118 2119 2120 2121 2122 2123 2124
        project
        project_member
        group = create(:group)
        link = create(:project_group_link, project: project, group: group)

        get api("/projects/#{project.id}", admin)

2125
        expect(response).to have_gitlab_http_status(:ok)
2126 2127 2128
        expect(json_response['id']).to eq(project.id)
        expect(json_response['description']).to eq(project.description)
        expect(json_response['default_branch']).to eq(project.default_branch)
2129 2130
        expect(json_response['tag_list']).to be_an Array # deprecated in favor of 'topics'
        expect(json_response['topics']).to be_an Array
2131 2132 2133 2134 2135
        expect(json_response['archived']).to be_falsey
        expect(json_response['visibility']).to be_present
        expect(json_response['ssh_url_to_repo']).to be_present
        expect(json_response['http_url_to_repo']).to be_present
        expect(json_response['web_url']).to be_present
2136
        expect(json_response['container_registry_image_prefix']).to eq("registry.example.org:5000/#{project.full_path}")
2137 2138 2139 2140 2141
        expect(json_response['owner']).to be_a Hash
        expect(json_response['name']).to eq(project.name)
        expect(json_response['path']).to be_present
        expect(json_response['issues_enabled']).to be_present
        expect(json_response['merge_requests_enabled']).to be_present
2142
        expect(json_response['can_create_merge_request_in']).to be_present
2143 2144 2145 2146
        expect(json_response['wiki_enabled']).to be_present
        expect(json_response['jobs_enabled']).to be_present
        expect(json_response['snippets_enabled']).to be_present
        expect(json_response['container_registry_enabled']).to be_present
2147
        expect(json_response['container_registry_access_level']).to be_present
2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162
        expect(json_response['created_at']).to be_present
        expect(json_response['last_activity_at']).to be_present
        expect(json_response['shared_runners_enabled']).to be_present
        expect(json_response['creator_id']).to be_present
        expect(json_response['namespace']).to be_present
        expect(json_response['avatar_url']).to be_nil
        expect(json_response['star_count']).to be_present
        expect(json_response['forks_count']).to be_present
        expect(json_response['public_jobs']).to be_present
        expect(json_response['shared_with_groups']).to be_an Array
        expect(json_response['shared_with_groups'].length).to eq(1)
        expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
        expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
        expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
        expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
2163
        expect(json_response['allow_merge_on_skipped_pipeline']).to eq(project.allow_merge_on_skipped_pipeline)
2164
        expect(json_response['restrict_user_defined_variables']).to eq(project.restrict_user_defined_variables?)
2165
        expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
2166
        expect(json_response['operations_access_level']).to be_present
2167
      end
2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188

      it 'exposes all necessary attributes' do
        create(:project_group_link, project: project)

        get api("/projects/#{project.id}", admin)

        diff = Set.new(json_response.keys) ^ Set.new(expected_keys)

        expect(diff).to be_empty, failure_message(diff)
      end

      def failure_message(diff)
        <<~MSG
          It looks like project's set of exposed attributes is different from the expected set.

          The following attributes are missing or newly added:
          #{diff.to_a.to_sentence}

          Please update #{project_attributes_file} file"
        MSG
      end
2189 2190 2191
    end

    context 'when authenticated as a regular user' do
2192 2193 2194
      before do
        project
        project_member
2195
        stub_container_registry_config(enabled: true, host_port: 'registry.example.org:5000')
2196
      end
2197

2198
      it 'returns a project by id', :aggregate_failures do
2199 2200
        group = create(:group)
        link = create(:project_group_link, project: project, group: group)
2201

2202
        get api("/projects/#{project.id}", user)
2203

2204
        expect(response).to have_gitlab_http_status(:ok)
2205 2206 2207
        expect(json_response['id']).to eq(project.id)
        expect(json_response['description']).to eq(project.description)
        expect(json_response['default_branch']).to eq(project.default_branch)
2208 2209
        expect(json_response['tag_list']).to be_an Array # deprecated in favor of 'topics'
        expect(json_response['topics']).to be_an Array
2210
        expect(json_response['archived']).to be_falsey
2211
        expect(json_response['visibility']).to be_present
2212 2213 2214
        expect(json_response['ssh_url_to_repo']).to be_present
        expect(json_response['http_url_to_repo']).to be_present
        expect(json_response['web_url']).to be_present
2215
        expect(json_response['container_registry_image_prefix']).to eq("registry.example.org:5000/#{project.full_path}")
2216 2217 2218 2219 2220
        expect(json_response['owner']).to be_a Hash
        expect(json_response['name']).to eq(project.name)
        expect(json_response['path']).to be_present
        expect(json_response['issues_enabled']).to be_present
        expect(json_response['merge_requests_enabled']).to be_present
2221
        expect(json_response['can_create_merge_request_in']).to be_present
2222
        expect(json_response['wiki_enabled']).to be_present
Toon Claes's avatar
Toon Claes committed
2223
        expect(json_response['jobs_enabled']).to be_present
2224
        expect(json_response['snippets_enabled']).to be_present
2225
        expect(json_response['snippets_access_level']).to be_present
2226
        expect(json_response['pages_access_level']).to be_present
2227 2228 2229
        expect(json_response['repository_access_level']).to be_present
        expect(json_response['issues_access_level']).to be_present
        expect(json_response['merge_requests_access_level']).to be_present
2230
        expect(json_response['forking_access_level']).to be_present
2231
        expect(json_response['analytics_access_level']).to be_present
2232 2233
        expect(json_response['wiki_access_level']).to be_present
        expect(json_response['builds_access_level']).to be_present
2234
        expect(json_response['operations_access_level']).to be_present
2235
        expect(json_response).to have_key('emails_disabled')
2236
        expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions)
2237
        expect(json_response['remove_source_branch_after_merge']).to be_truthy
2238
        expect(json_response['container_registry_enabled']).to be_present
2239
        expect(json_response['container_registry_access_level']).to be_present
2240 2241 2242 2243 2244
        expect(json_response['created_at']).to be_present
        expect(json_response['last_activity_at']).to be_present
        expect(json_response['shared_runners_enabled']).to be_present
        expect(json_response['creator_id']).to be_present
        expect(json_response['namespace']).to be_present
2245 2246
        expect(json_response['import_status']).to be_present
        expect(json_response).to include("import_error")
2247
        expect(json_response).to have_key('avatar_url')
2248 2249
        expect(json_response['star_count']).to be_present
        expect(json_response['forks_count']).to be_present
Toon Claes's avatar
Toon Claes committed
2250
        expect(json_response['public_jobs']).to be_present
2251
        expect(json_response).to have_key('ci_config_path')
2252 2253 2254 2255
        expect(json_response['shared_with_groups']).to be_an Array
        expect(json_response['shared_with_groups'].length).to eq(1)
        expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
        expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
2256
        expect(json_response['shared_with_groups'][0]['group_full_path']).to eq(group.full_path)
2257
        expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
2258
        expect(json_response['shared_with_groups'][0]).to have_key('expires_at')
2259
        expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
2260
        expect(json_response['allow_merge_on_skipped_pipeline']).to eq(project.allow_merge_on_skipped_pipeline)
2261
        expect(json_response['restrict_user_defined_variables']).to eq(project.restrict_user_defined_variables?)
2262
        expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
2263
        expect(json_response['ci_default_git_depth']).to eq(project.ci_default_git_depth)
2264
        expect(json_response['ci_forward_deployment_enabled']).to eq(project.ci_forward_deployment_enabled)
2265
        expect(json_response['merge_method']).to eq(project.merge_method.to_s)
2266
        expect(json_response['squash_option']).to eq(project.squash_option.to_s)
2267
        expect(json_response['readme_url']).to eq(project.readme_url)
2268
        expect(json_response).to have_key 'packages_enabled'
2269
        expect(json_response['keep_latest_artifact']).to be_present
2270
      end
2271

2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282
      it 'returns a group link with expiration date' do
        group = create(:group)
        expires_at = 5.days.from_now.to_date
        link = create(:project_group_link, project: project, group: group, expires_at: expires_at)

        get api("/projects/#{project.id}", user)

        expect(json_response['shared_with_groups']).to be_an Array
        expect(json_response['shared_with_groups'].length).to eq(1)
        expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
        expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
2283
        expect(json_response['shared_with_groups'][0]['group_full_path']).to eq(group.full_path)
2284 2285 2286 2287
        expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
        expect(json_response['shared_with_groups'][0]['expires_at']).to eq(expires_at.to_s)
      end

2288 2289
      it 'returns a project by path name' do
        get api("/projects/#{project.id}", user)
2290
        expect(response).to have_gitlab_http_status(:ok)
2291 2292
        expect(json_response['name']).to eq(project.name)
      end
2293

2294
      it 'returns a 404 error if not found' do
2295
        get api("/projects/#{non_existing_record_id}", user)
2296
        expect(response).to have_gitlab_http_status(:not_found)
2297 2298
        expect(json_response['message']).to eq('404 Project Not Found')
      end
2299

2300 2301 2302
      it 'returns a 404 error if user is not a member' do
        other_user = create(:user)
        get api("/projects/#{project.id}", other_user)
2303
        expect(response).to have_gitlab_http_status(:not_found)
2304
      end
2305

2306 2307
      it 'handles users with dots' do
        dot_user = create(:user, username: 'dot.user')
2308
        project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace)
2309

2310
        get api("/projects/#{CGI.escape(project.full_path)}", dot_user)
2311
        expect(response).to have_gitlab_http_status(:ok)
2312
        expect(json_response['name']).to eq(project.name)
2313 2314
      end

2315 2316
      it 'exposes namespace fields' do
        get api("/projects/#{project.id}", user)
2317

2318
        expect(response).to have_gitlab_http_status(:ok)
2319 2320 2321 2322 2323
        expect(json_response['namespace']).to eq({
          'id' => user.namespace.id,
          'name' => user.namespace.name,
          'path' => user.namespace.path,
          'kind' => user.namespace.kind,
2324
          'full_path' => user.namespace.full_path,
2325 2326 2327
          'parent_id' => nil,
          'avatar_url' => user.avatar_url,
          'web_url' => Gitlab::Routing.url_helpers.user_url(user)
2328
        })
2329 2330
      end

2331 2332 2333
      it "does not include license fields by default" do
        get api("/projects/#{project.id}", user)

2334
        expect(response).to have_gitlab_http_status(:ok)
2335 2336 2337 2338
        expect(json_response).not_to include('license', 'license_url')
      end

      it 'includes license fields when requested' do
blackst0ne's avatar
blackst0ne committed
2339
        get api("/projects/#{project.id}", user), params: { license: true }
2340

2341
        expect(response).to have_gitlab_http_status(:ok)
2342 2343 2344 2345 2346 2347 2348 2349 2350
        expect(json_response['license']).to eq({
          'key' => project.repository.license.key,
          'name' => project.repository.license.name,
          'nickname' => project.repository.license.nickname,
          'html_url' => project.repository.license.url,
          'source_url' => project.repository.license.meta['source']
        })
      end

2351 2352
      it "does not include statistics by default" do
        get api("/projects/#{project.id}", user)
2353

2354
        expect(response).to have_gitlab_http_status(:ok)
2355 2356
        expect(json_response).not_to include 'statistics'
      end
2357

2358
      it "includes statistics if requested" do
blackst0ne's avatar
blackst0ne committed
2359
        get api("/projects/#{project.id}", user), params: { statistics: true }
2360

2361
        expect(response).to have_gitlab_http_status(:ok)
2362
        expect(json_response).to include 'statistics'
2363
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
2364

2365 2366 2367 2368 2369 2370
      context "and the project has a private repository" do
        let(:project) { create(:project, :public, :repository, :repository_private) }

        it "does not include statistics if user is not a member" do
          get api("/projects/#{project.id}", user), params: { statistics: true }

2371
          expect(response).to have_gitlab_http_status(:ok)
2372 2373 2374 2375 2376 2377 2378 2379
          expect(json_response).not_to include 'statistics'
        end

        it "includes statistics if user is a member" do
          project.add_developer(user)

          get api("/projects/#{project.id}", user), params: { statistics: true }

2380
          expect(response).to have_gitlab_http_status(:ok)
2381 2382
          expect(json_response).to include 'statistics'
        end
2383 2384 2385 2386 2387 2388 2389

        it "includes statistics also when repository is disabled" do
          project.add_developer(user)
          project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)

          get api("/projects/#{project.id}", user), params: { statistics: true }

2390
          expect(response).to have_gitlab_http_status(:ok)
2391 2392
          expect(json_response).to include 'statistics'
        end
2393 2394
      end

2395 2396
      it "includes import_error if user can admin project" do
        get api("/projects/#{project.id}", user)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
2397

2398
        expect(response).to have_gitlab_http_status(:ok)
2399
        expect(json_response).to include("import_error")
2400 2401
      end

2402 2403
      it "does not include import_error if user cannot admin project" do
        get api("/projects/#{project.id}", user3)
2404

2405
        expect(response).to have_gitlab_http_status(:ok)
2406 2407
        expect(json_response).not_to include("import_error")
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
2408

2409
      it 'returns 404 when project is marked for deletion' do
2410
        project.update!(pending_delete: true)
2411 2412 2413

        get api("/projects/#{project.id}", user)

2414
        expect(response).to have_gitlab_http_status(:not_found)
2415 2416 2417
        expect(json_response['message']).to eq('404 Project Not Found')
      end

2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433
      context 'links exposure' do
        it 'exposes related resources full URIs' do
          get api("/projects/#{project.id}", user)

          links = json_response['_links']

          expect(links['self']).to end_with("/api/v4/projects/#{project.id}")
          expect(links['issues']).to end_with("/api/v4/projects/#{project.id}/issues")
          expect(links['merge_requests']).to end_with("/api/v4/projects/#{project.id}/merge_requests")
          expect(links['repo_branches']).to end_with("/api/v4/projects/#{project.id}/repository/branches")
          expect(links['labels']).to end_with("/api/v4/projects/#{project.id}/labels")
          expect(links['events']).to end_with("/api/v4/projects/#{project.id}/events")
          expect(links['members']).to end_with("/api/v4/projects/#{project.id}/members")
        end

        it 'filters related URIs when their feature is not enabled' do
2434
          project = create(:project, :public,
2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449
                           :merge_requests_disabled,
                           :issues_disabled,
                           creator_id: user.id,
                           namespace: user.namespace)

          get api("/projects/#{project.id}", user)

          links = json_response['_links']

          expect(links.has_key?('merge_requests')).to be_falsy
          expect(links.has_key?('issues')).to be_falsy
          expect(links['self']).to end_with("/api/v4/projects/#{project.id}")
        end
      end

2450 2451 2452 2453 2454 2455
      context 'the project is a fork' do
        it 'shows details of a visible fork parent' do
          fork = fork_project(project, user)

          get api("/projects/#{fork.id}", user)

2456
          expect(response).to have_gitlab_http_status(:ok)
2457 2458 2459 2460 2461 2462 2463 2464 2465 2466
          expect(json_response['forked_from_project']).to include('id' => project.id)
        end

        it 'hides details of a hidden fork parent' do
          fork = fork_project(project, user)
          fork_user = create(:user)
          fork.team.add_developer(fork_user)

          get api("/projects/#{fork.id}", fork_user)

2467
          expect(response).to have_gitlab_http_status(:ok)
2468 2469 2470 2471
          expect(json_response['forked_from_project']).to be_nil
        end
      end

2472 2473
      describe 'permissions' do
        context 'all projects' do
2474
          before do
2475
            project.add_maintainer(user)
2476
          end
2477 2478 2479 2480

          it 'contains permission information' do
            get api("/projects", user)

2481
            expect(response).to have_gitlab_http_status(:ok)
2482
            expect(json_response.first['permissions']['project_access']['access_level'])
2483
            .to eq(Gitlab::Access::MAINTAINER)
2484 2485 2486 2487 2488 2489
            expect(json_response.first['permissions']['group_access']).to be_nil
          end
        end

        context 'personal project' do
          it 'sets project access and returns 200' do
2490
            project.add_maintainer(user)
2491 2492
            get api("/projects/#{project.id}", user)

2493
            expect(response).to have_gitlab_http_status(:ok)
2494
            expect(json_response['permissions']['project_access']['access_level'])
2495
            .to eq(Gitlab::Access::MAINTAINER)
2496 2497
            expect(json_response['permissions']['group_access']).to be_nil
          end
2498
        end
2499

2500
        context 'group project' do
2501
          let(:project2) { create(:project, group: create(:group)) }
2502

2503 2504 2505
          before do
            project2.group.add_owner(user)
          end
2506

2507 2508
          it 'sets the owner and return 200' do
            get api("/projects/#{project2.id}", user)
2509

2510
            expect(response).to have_gitlab_http_status(:ok)
2511
            expect(json_response['permissions']['project_access']).to be_nil
2512 2513
            expect(json_response['permissions']['group_access']['access_level'])
            .to eq(Gitlab::Access::OWNER)
2514
          end
2515
        end
2516

2517
        context 'nested group project' do
2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528
          let(:group) { create(:group) }
          let(:nested_group) { create(:group, parent: group) }
          let(:project2) { create(:project, group: nested_group) }

          before do
            project2.group.parent.add_owner(user)
          end

          it 'sets group access and return 200' do
            get api("/projects/#{project2.id}", user)

2529
            expect(response).to have_gitlab_http_status(:ok)
2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542
            expect(json_response['permissions']['project_access']).to be_nil
            expect(json_response['permissions']['group_access']['access_level'])
            .to eq(Gitlab::Access::OWNER)
          end

          context 'with various access levels across nested groups' do
            before do
              project2.group.add_maintainer(user)
            end

            it 'sets the maximum group access and return 200' do
              get api("/projects/#{project2.id}", user)

2543
              expect(response).to have_gitlab_http_status(:ok)
2544 2545 2546 2547 2548 2549
              expect(json_response['permissions']['project_access']).to be_nil
              expect(json_response['permissions']['group_access']['access_level'])
              .to eq(Gitlab::Access::OWNER)
            end
          end
        end
2550
      end
2551 2552 2553 2554 2555 2556 2557 2558 2559

      context 'when project belongs to a group namespace' do
        let(:group) { create(:group, :with_avatar) }
        let(:project) { create(:project, namespace: group) }
        let!(:project_member) { create(:project_member, :developer, user: user, project: project) }

        it 'returns group web_url and avatar_url' do
          get api("/projects/#{project.id}", user)

2560
          expect(response).to have_gitlab_http_status(:ok)
2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574

          group_data = json_response['namespace']
          expect(group_data['web_url']).to eq(group.web_url)
          expect(group_data['avatar_url']).to eq(group.avatar_url)
        end
      end

      context 'when project belongs to a user namespace' do
        let(:user) { create(:user) }
        let(:project) { create(:project, namespace: user.namespace) }

        it 'returns user web_url and avatar_url' do
          get api("/projects/#{project.id}", user)

2575
          expect(response).to have_gitlab_http_status(:ok)
2576 2577 2578 2579 2580 2581

          user_data = json_response['namespace']
          expect(user_data['web_url']).to eq("http://localhost/#{user.username}")
          expect(user_data['avatar_url']).to eq(user.avatar_url)
        end
      end
2582
    end
2583 2584 2585 2586 2587 2588 2589 2590

    it_behaves_like 'storing arguments in the application context' do
      let_it_be(:user) { create(:user) }
      let_it_be(:project) { create(:project, :public) }
      let(:expected_params) { { user: user.username, project: project.full_path } }

      subject { get api("/projects/#{project.id}", user) }
    end
2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611

    describe 'repository_storage attribute' do
      before do
        get api("/projects/#{project.id}", user)
      end

      context 'when authenticated as an admin' do
        let(:user) { create(:admin) }

        it 'returns repository_storage attribute' do
          expect(response).to have_gitlab_http_status(:ok)
          expect(json_response['repository_storage']).to eq(project.repository_storage)
        end
      end

      context 'when authenticated as a regular user' do
        it 'does not return repository_storage attribute' do
          expect(json_response).not_to have_key('repository_storage')
        end
      end
    end
2612 2613 2614 2615 2616 2617 2618

    it 'exposes service desk attributes' do
      get api("/projects/#{project.id}", user)

      expect(json_response).to have_key 'service_desk_enabled'
      expect(json_response).to have_key 'service_desk_address'
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
2619
  end
2620

2621 2622
  describe 'GET /projects/:id/users' do
    shared_examples_for 'project users response' do
2623 2624 2625 2626 2627 2628 2629 2630
      let(:reporter_1) { create(:user) }
      let(:reporter_2) { create(:user) }

      before do
        project.add_reporter(reporter_1)
        project.add_reporter(reporter_2)
      end

2631 2632
      it 'returns the project users' do
        get api("/projects/#{project.id}/users", current_user)
2633

2634 2635
        user = project.namespace.owner

2636
        expect(response).to have_gitlab_http_status(:ok)
2637
        expect(response).to include_pagination_headers
2638
        expect(json_response).to be_an Array
2639
        expect(json_response.size).to eq(3)
2640 2641

        first_user = json_response.first
2642 2643
        expect(first_user['username']).to eq(user.username)
        expect(first_user['name']).to eq(user.name)
2644
        expect(first_user.keys).to include(*%w[name username id state avatar_url web_url])
2645 2646 2647

        ids = json_response.map { |raw_user| raw_user['id'] }
        expect(ids).to eq([user.id, reporter_1.id, reporter_2.id])
2648
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
2649 2650
    end

2651 2652
    context 'when unauthenticated' do
      it_behaves_like 'project users response' do
2653
        let(:project) { create(:project, :public) }
2654 2655
        let(:current_user) { nil }
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
2656 2657
    end

2658 2659
    context 'when authenticated' do
      context 'valid request' do
2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679
        context 'when sort_by_project_authorizations_user_id FF is off' do
          before do
            stub_feature_flags(sort_by_project_users_by_project_authorizations_user_id: false)
          end

          it_behaves_like 'project users response' do
            let(:project) { project4 }
            let(:current_user) { user4 }
          end
        end

        context 'when sort_by_project_authorizations_user_id FF is on' do
          before do
            stub_feature_flags(sort_by_project_users_by_project_authorizations_user_id: true)
          end

          it_behaves_like 'project users response' do
            let(:project) { project4 }
            let(:current_user) { user4 }
          end
2680 2681 2682 2683
        end
      end

      it 'returns a 404 error if not found' do
2684
        get api("/projects/#{non_existing_record_id}/users", user)
2685

2686
        expect(response).to have_gitlab_http_status(:not_found)
2687 2688 2689 2690 2691 2692 2693 2694
        expect(json_response['message']).to eq('404 Project Not Found')
      end

      it 'returns a 404 error if user is not a member' do
        other_user = create(:user)

        get api("/projects/#{project.id}/users", other_user)

2695
        expect(response).to have_gitlab_http_status(:not_found)
2696
      end
2697 2698 2699 2700 2701 2702 2703

      it 'filters out users listed in skip_users' do
        other_user = create(:user)
        project.team.add_developer(other_user)

        get api("/projects/#{project.id}/users?skip_users=#{user.id}", user)

2704
        expect(response).to have_gitlab_http_status(:ok)
2705 2706
        expect(json_response.size).to eq(2)
        expect(json_response.map { |m| m['id'] }).not_to include(user.id)
2707
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
2708 2709 2710
    end
  end

2711
  describe 'fork management' do
2712 2713
    let(:project_fork_target) { create(:project) }
    let(:project_fork_source) { create(:project, :public) }
2714
    let(:private_project_fork_source) { create(:project, :private) }
2715

2716
    describe 'POST /projects/:id/fork/:forked_from_id' do
2717 2718 2719 2720
      context 'user is a developer' do
        before do
          project_fork_target.add_developer(user)
        end
2721

2722 2723
        it 'denies project to be forked from an existing project' do
          post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
2724

2725
          expect(response).to have_gitlab_http_status(:forbidden)
2726
        end
2727 2728
      end

2729
      it 'refreshes the forks count cache' do
2730
        expect(project_fork_source.forks_count).to be_zero
2731 2732 2733 2734 2735 2736
      end

      context 'user is maintainer' do
        before do
          project_fork_target.add_maintainer(user)
        end
2737

2738 2739
        it 'allows project to be forked from an existing project' do
          expect(project_fork_target).not_to be_forked
2740

2741 2742
          post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
          project_fork_target.reload
2743

2744
          expect(response).to have_gitlab_http_status(:created)
2745
          expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
2746
          expect(project_fork_target.fork_network_member).to be_present
2747 2748 2749
          expect(project_fork_target).to be_forked
        end

2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760
        it 'fails without permission from forked_from project' do
          project_fork_source.project_feature.update_attribute(:forking_access_level, ProjectFeature::PRIVATE)

          post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)

          expect(response).to have_gitlab_http_status(:forbidden)
          expect(project_fork_target.forked_from_project).to be_nil
          expect(project_fork_target.fork_network_member).not_to be_present
          expect(project_fork_target).not_to be_forked
        end

2761 2762 2763
        it 'denies project to be forked from a private project' do
          post api("/projects/#{project_fork_target.id}/fork/#{private_project_fork_source.id}", user)

2764
          expect(response).to have_gitlab_http_status(:not_found)
2765
        end
2766 2767
      end

2768 2769 2770 2771 2772 2773
      context 'user is admin' do
        it 'allows project to be forked from an existing project' do
          expect(project_fork_target).not_to be_forked

          post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)

2774
          expect(response).to have_gitlab_http_status(:created)
2775 2776 2777 2778 2779
        end

        it 'allows project to be forked from a private project' do
          post api("/projects/#{project_fork_target.id}/fork/#{private_project_fork_source.id}", admin)

2780
          expect(response).to have_gitlab_http_status(:created)
2781 2782 2783 2784 2785 2786 2787 2788 2789
        end

        it 'refreshes the forks count cachce' do
          expect do
            post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
          end.to change(project_fork_source, :forks_count).by(1)
        end

        it 'fails if forked_from project which does not exist' do
2790
          post api("/projects/#{project_fork_target.id}/fork/#{non_existing_record_id}", admin)
2791
          expect(response).to have_gitlab_http_status(:not_found)
2792 2793 2794 2795 2796 2797 2798 2799 2800 2801
        end

        it 'fails with 409 if already forked' do
          other_project_fork_source = create(:project, :public)

          Projects::ForkService.new(project_fork_source, admin).execute(project_fork_target)

          post api("/projects/#{project_fork_target.id}/fork/#{other_project_fork_source.id}", admin)
          project_fork_target.reload

2802
          expect(response).to have_gitlab_http_status(:conflict)
2803 2804 2805
          expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
          expect(project_fork_target).to be_forked
        end
2806 2807 2808
      end
    end

2809
    describe 'DELETE /projects/:id/fork' do
2810
      it "is not visible to users outside group" do
2811
        delete api("/projects/#{project_fork_target.id}/fork", user)
2812
        expect(response).to have_gitlab_http_status(:not_found)
2813 2814
      end

2815
      context 'when users belong to project group' do
2816
        let(:project_fork_target) { create(:project, group: create(:group)) }
2817

2818 2819 2820 2821 2822
        before do
          project_fork_target.group.add_owner user
          project_fork_target.group.add_developer user2
        end

2823 2824 2825 2826
        context 'for a forked project' do
          before do
            post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
            project_fork_target.reload
2827 2828
            expect(project_fork_target.forked_from_project).to be_present
            expect(project_fork_target).to be_forked
2829
          end
2830

2831 2832
          it 'makes forked project unforked' do
            delete api("/projects/#{project_fork_target.id}/fork", admin)
2833

2834
            expect(response).to have_gitlab_http_status(:no_content)
2835 2836
            project_fork_target.reload
            expect(project_fork_target.forked_from_project).to be_nil
2837
            expect(project_fork_target).not_to be_forked
2838
          end
2839

2840 2841 2842 2843 2844 2845 2846
          it_behaves_like '412 response' do
            let(:request) { api("/projects/#{project_fork_target.id}/fork", admin) }
          end
        end

        it 'is forbidden to non-owner users' do
          delete api("/projects/#{project_fork_target.id}/fork", user2)
2847
          expect(response).to have_gitlab_http_status(:forbidden)
2848 2849
        end

2850
        it 'is idempotent if not forked' do
2851 2852
          expect(project_fork_target.forked_from_project).to be_nil
          delete api("/projects/#{project_fork_target.id}/fork", admin)
2853
          expect(response).to have_gitlab_http_status(:not_modified)
2854 2855
          expect(project_fork_target.reload.forked_from_project).to be_nil
        end
2856 2857
      end
    end
2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871

    describe 'GET /projects/:id/forks' do
      let(:private_fork) { create(:project, :private, :empty_repo) }
      let(:member) { create(:user) }
      let(:non_member) { create(:user) }

      before do
        private_fork.add_developer(member)
      end

      context 'for a forked project' do
        before do
          post api("/projects/#{private_fork.id}/fork/#{project_fork_source.id}", admin)
          private_fork.reload
2872 2873
          expect(private_fork.forked_from_project).to be_present
          expect(private_fork).to be_forked
2874 2875 2876 2877 2878 2879 2880 2881 2882
          project_fork_source.reload
          expect(project_fork_source.forks.length).to eq(1)
          expect(project_fork_source.forks).to include(private_fork)
        end

        context 'for a user that can access the forks' do
          it 'returns the forks' do
            get api("/projects/#{project_fork_source.id}/forks", member)

2883
            expect(response).to have_gitlab_http_status(:ok)
2884 2885 2886 2887 2888 2889 2890 2891 2892 2893
            expect(response).to include_pagination_headers
            expect(json_response.length).to eq(1)
            expect(json_response[0]['name']).to eq(private_fork.name)
          end
        end

        context 'for a user that cannot access the forks' do
          it 'returns an empty array' do
            get api("/projects/#{project_fork_source.id}/forks", non_member)

2894
            expect(response).to have_gitlab_http_status(:ok)
2895 2896 2897 2898 2899 2900 2901 2902 2903 2904
            expect(response).to include_pagination_headers
            expect(json_response.length).to eq(0)
          end
        end
      end

      context 'for a non-forked project' do
        it 'returns an empty array' do
          get api("/projects/#{project_fork_source.id}/forks")

2905
          expect(response).to have_gitlab_http_status(:ok)
2906 2907 2908 2909 2910
          expect(response).to include_pagination_headers
          expect(json_response.length).to eq(0)
        end
      end
    end
2911
  end
2912

2913
  describe "POST /projects/:id/share" do
2914 2915
    let_it_be(:group) { create(:group, :private) }
    let_it_be(:group_user) { create(:user) }
2916

2917 2918
    before do
      group.add_developer(user)
2919
      group.add_developer(group_user)
2920
    end
2921

2922
    it "shares project with group" do
2923 2924
      expires_at = 10.days.from_now.to_date

2925
      expect do
blackst0ne's avatar
blackst0ne committed
2926
        post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at }
2927 2928
      end.to change { ProjectGroupLink.count }.by(1)

2929
      expect(response).to have_gitlab_http_status(:created)
2930 2931 2932
      expect(json_response['group_id']).to eq(group.id)
      expect(json_response['group_access']).to eq(Gitlab::Access::DEVELOPER)
      expect(json_response['expires_at']).to eq(expires_at.to_s)
2933 2934
    end

2935
    it 'updates project authorization', :sidekiq_inline do
2936 2937 2938 2939 2940 2941 2942
      expect do
        post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER }
      end.to(
        change { group_user.can?(:read_project, project) }.from(false).to(true)
      )
    end

2943
    it "returns a 400 error when group id is not given" do
blackst0ne's avatar
blackst0ne committed
2944
      post api("/projects/#{project.id}/share", user), params: { group_access: Gitlab::Access::DEVELOPER }
2945
      expect(response).to have_gitlab_http_status(:bad_request)
2946 2947
    end

2948
    it "returns a 400 error when access level is not given" do
blackst0ne's avatar
blackst0ne committed
2949
      post api("/projects/#{project.id}/share", user), params: { group_id: group.id }
2950
      expect(response).to have_gitlab_http_status(:bad_request)
2951 2952
    end

2953
    it "returns a 400 error when sharing is disabled" do
2954
      project.namespace.update!(share_with_group_lock: true)
blackst0ne's avatar
blackst0ne committed
2955
      post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER }
2956
      expect(response).to have_gitlab_http_status(:bad_request)
2957 2958
    end

2959 2960 2961
    it 'returns a 404 error when user cannot read group' do
      private_group = create(:group, :private)

blackst0ne's avatar
blackst0ne committed
2962
      post api("/projects/#{project.id}/share", user), params: { group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER }
2963

2964
      expect(response).to have_gitlab_http_status(:not_found)
2965 2966
    end

2967
    it 'returns a 404 error when group does not exist' do
2968
      post api("/projects/#{project.id}/share", user), params: { group_id: non_existing_record_id, group_access: Gitlab::Access::DEVELOPER }
2969

2970
      expect(response).to have_gitlab_http_status(:not_found)
2971 2972
    end

2973
    it "returns a 400 error when wrong params passed" do
2974
      post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: non_existing_record_access_level }
2975

2976
      expect(response).to have_gitlab_http_status(:bad_request)
2977
      expect(json_response['error']).to eq 'group_access does not have a valid value'
2978
    end
2979 2980 2981 2982 2983 2984 2985

    it "returns a 409 error when link is not saved" do
      allow(::Projects::GroupLinks::CreateService).to receive_message_chain(:new, :execute)
        .and_return({ status: :error, http_status: 409, message: 'error' })

      post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER }

2986
      expect(response).to have_gitlab_http_status(:conflict)
2987
    end
2988 2989
  end

2990
  describe 'DELETE /projects/:id/share/:group_id' do
2991
    context 'for a valid group' do
2992 2993
      let_it_be(:group) { create(:group, :private) }
      let_it_be(:group_user) { create(:user) }
2994 2995

      before do
2996 2997
        group.add_developer(group_user)

2998 2999 3000 3001 3002
        create(:project_group_link, group: group, project: project)
      end

      it 'returns 204 when deleting a group share' do
        delete api("/projects/#{project.id}/share/#{group.id}", user)
3003

3004
        expect(response).to have_gitlab_http_status(:no_content)
3005 3006
        expect(project.project_group_links).to be_empty
      end
3007

3008
      it 'updates project authorization', :sidekiq_inline do
3009 3010 3011 3012 3013 3014 3015
        expect do
          delete api("/projects/#{project.id}/share/#{group.id}", user)
        end.to(
          change { group_user.can?(:read_project, project) }.from(true).to(false)
        )
      end

3016 3017 3018
      it_behaves_like '412 response' do
        let(:request) { api("/projects/#{project.id}/share/#{group.id}", user) }
      end
3019 3020
    end

3021 3022 3023
    it 'returns a 400 when group id is not an integer' do
      delete api("/projects/#{project.id}/share/foo", user)

3024
      expect(response).to have_gitlab_http_status(:bad_request)
3025 3026
    end

3027
    it 'returns a 404 error when group link does not exist' do
3028
      delete api("/projects/#{project.id}/share/#{non_existing_record_id}", user)
3029

3030
      expect(response).to have_gitlab_http_status(:not_found)
3031 3032 3033
    end

    it 'returns a 404 error when project does not exist' do
3034
      delete api("/projects/#{non_existing_record_id}/share/#{non_existing_record_id}", user)
3035

3036
      expect(response).to have_gitlab_http_status(:not_found)
3037 3038
    end
  end
3039

3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092
  describe 'POST /projects/:id/import_project_members/:project_id' do
    let_it_be(:project2) { create(:project) }
    let_it_be(:project2_user) { create(:user) }

    before_all do
      project.add_maintainer(user)
      project2.add_maintainer(user)
      project2.add_developer(project2_user)
    end

    it 'returns 200 when it successfully imports members from another project' do
      expect do
        post api("/projects/#{project.id}/import_project_members/#{project2.id}", user)
      end.to change { project.members.count }.by(2)

      expect(response).to have_gitlab_http_status(:created)
      expect(json_response['message']).to eq('Successfully imported')
    end

    it 'returns 404 if the source project does not exist' do
      expect do
        post api("/projects/#{project.id}/import_project_members/#{non_existing_record_id}", user)
      end.not_to change { project.members.count }

      expect(response).to have_gitlab_http_status(:not_found)
      expect(json_response['message']).to eq('404 Project Not Found')
    end

    it 'returns 404 if the target project members cannot be administered by the requester' do
      private_project = create(:project, :private)

      expect do
        post api("/projects/#{private_project.id}/import_project_members/#{project2.id}", user)
      end.not_to change { project.members.count }

      expect(response).to have_gitlab_http_status(:not_found)
      expect(json_response['message']).to eq('404 Project Not Found')
    end

    it 'returns 422 if the import failed for valid projects' do
      allow_next_instance_of(::ProjectTeam) do |project_team|
        allow(project_team).to receive(:import).and_return(false)
      end

      expect do
        post api("/projects/#{project.id}/import_project_members/#{project2.id}", user)
      end.not_to change { project.members.count }

      expect(response).to have_gitlab_http_status(:unprocessable_entity)
      expect(json_response['message']).to eq('Import failed')
    end
  end

3093
  describe 'PUT /projects/:id' do
3094 3095 3096 3097 3098 3099 3100 3101 3102 3103
    before do
      expect(project).to be_persisted
      expect(user).to be_persisted
      expect(user3).to be_persisted
      expect(user4).to be_persisted
      expect(project3).to be_persisted
      expect(project4).to be_persisted
      expect(project_member2).to be_persisted
      expect(project_member).to be_persisted
    end
3104

3105 3106 3107 3108 3109
    describe 'updating packages_enabled attribute' do
      it 'is enabled by default' do
        expect(project.packages_enabled).to be true
      end

3110 3111
      it 'disables project packages feature' do
        put(api("/projects/#{project.id}", user), params: { packages_enabled: false })
3112

3113 3114 3115
        expect(response).to have_gitlab_http_status(:ok)
        expect(project.reload.packages_enabled).to be false
        expect(json_response['packages_enabled']).to eq(false)
3116 3117 3118
      end
    end

3119 3120 3121 3122 3123 3124 3125 3126
    it 'sets container_registry_access_level', :aggregate_failures do
      put api("/projects/#{project.id}", user), params: { container_registry_access_level: 'private' }

      expect(response).to have_gitlab_http_status(:ok)
      expect(json_response['container_registry_access_level']).to eq('private')
      expect(Project.find_by(path: project[:path]).container_registry_access_level).to eq(ProjectFeature::PRIVATE)
    end

3127 3128 3129 3130 3131 3132 3133 3134 3135 3136
    it 'sets container_registry_enabled' do
      project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED)

      put(api("/projects/#{project.id}", user), params: { container_registry_enabled: true })

      expect(response).to have_gitlab_http_status(:ok)
      expect(json_response['container_registry_enabled']).to eq(true)
      expect(project.reload.container_registry_access_level).to eq(ProjectFeature::ENABLED)
    end

3137 3138
    it 'returns 400 when nothing sent' do
      project_param = {}
winniehell's avatar
winniehell committed
3139

blackst0ne's avatar
blackst0ne committed
3140
      put api("/projects/#{project.id}", user), params: project_param
winniehell's avatar
winniehell committed
3141

3142
      expect(response).to have_gitlab_http_status(:bad_request)
3143 3144
      expect(json_response['error']).to match('at least one parameter must be provided')
    end
3145 3146

    context 'when unauthenticated' do
3147
      it 'returns authentication error' do
3148
        project_param = { name: 'bar' }
winniehell's avatar
winniehell committed
3149

blackst0ne's avatar
blackst0ne committed
3150
        put api("/projects/#{project.id}"), params: project_param
winniehell's avatar
winniehell committed
3151

3152
        expect(response).to have_gitlab_http_status(:unauthorized)
3153 3154 3155 3156
      end
    end

    context 'when authenticated as project owner' do
3157
      it 'updates name' do
3158
        project_param = { name: 'bar' }
winniehell's avatar
winniehell committed
3159

blackst0ne's avatar
blackst0ne committed
3160
        put api("/projects/#{project.id}", user), params: project_param
winniehell's avatar
winniehell committed
3161

3162
        expect(response).to have_gitlab_http_status(:ok)
winniehell's avatar
winniehell committed
3163

3164
        project_param.each_pair do |k, v|
3165
          expect(json_response[k.to_s]).to eq(v)
3166 3167 3168
        end
      end

3169
      it 'updates visibility_level' do
3170
        project_param = { visibility: 'public' }
winniehell's avatar
winniehell committed
3171

blackst0ne's avatar
blackst0ne committed
3172
        put api("/projects/#{project3.id}", user), params: project_param
winniehell's avatar
winniehell committed
3173

3174
        expect(response).to have_gitlab_http_status(:ok)
winniehell's avatar
winniehell committed
3175

3176
        project_param.each_pair do |k, v|
3177
          expect(json_response[k.to_s]).to eq(v)
3178 3179 3180
        end
      end

3181
      it 'updates visibility_level from public to private' do
3182
        project3.update!({ visibility_level: Gitlab::VisibilityLevel::PUBLIC })
3183
        project_param = { visibility: 'private' }
3184

blackst0ne's avatar
blackst0ne committed
3185
        put api("/projects/#{project3.id}", user), params: project_param
winniehell's avatar
winniehell committed
3186

3187
        expect(response).to have_gitlab_http_status(:ok)
winniehell's avatar
winniehell committed
3188

3189 3190 3191
        project_param.each_pair do |k, v|
          expect(json_response[k.to_s]).to eq(v)
        end
winniehell's avatar
winniehell committed
3192

3193
        expect(json_response['visibility']).to eq('private')
3194 3195
      end

3196
      it 'does not update name to existing name' do
3197
        project_param = { name: project3.name }
winniehell's avatar
winniehell committed
3198

blackst0ne's avatar
blackst0ne committed
3199
        put api("/projects/#{project.id}", user), params: project_param
winniehell's avatar
winniehell committed
3200

3201
        expect(response).to have_gitlab_http_status(:bad_request)
3202
        expect(json_response['message']['name']).to eq(['has already been taken'])
3203 3204
      end

3205 3206 3207
      it 'updates request_access_enabled' do
        project_param = { request_access_enabled: false }

blackst0ne's avatar
blackst0ne committed
3208
        put api("/projects/#{project.id}", user), params: project_param
3209

3210
        expect(response).to have_gitlab_http_status(:ok)
3211 3212 3213
        expect(json_response['request_access_enabled']).to eq(false)
      end

3214
      it 'updates path & name to existing path & name in different namespace' do
3215
        project_param = { path: project4.path, name: project4.name }
winniehell's avatar
winniehell committed
3216

blackst0ne's avatar
blackst0ne committed
3217
        put api("/projects/#{project3.id}", user), params: project_param
winniehell's avatar
winniehell committed
3218

3219
        expect(response).to have_gitlab_http_status(:ok)
winniehell's avatar
winniehell committed
3220

3221
        project_param.each_pair do |k, v|
3222
          expect(json_response[k.to_s]).to eq(v)
3223 3224
        end
      end
winniehell's avatar
winniehell committed
3225

3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237
      it 'updates default_branch' do
        project_param = { default_branch: 'something_else' }

        put api("/projects/#{project.id}", user), params: project_param

        expect(response).to have_gitlab_http_status(:ok)

        project_param.each_pair do |k, v|
          expect(json_response[k.to_s]).to eq(v)
        end
      end

winniehell's avatar
winniehell committed
3238 3239
      it 'updates jobs_enabled' do
        project_param = { jobs_enabled: true }
winniehell's avatar
winniehell committed
3240

blackst0ne's avatar
blackst0ne committed
3241
        put api("/projects/#{project3.id}", user), params: project_param
winniehell's avatar
winniehell committed
3242

3243
        expect(response).to have_gitlab_http_status(:ok)
winniehell's avatar
winniehell committed
3244

3245
        project_param.each_pair do |k, v|
3246
          expect(json_response[k.to_s]).to eq(v)
3247 3248
        end
      end
3249

3250 3251 3252 3253 3254
      it 'updates builds_access_level' do
        project_param = { builds_access_level: 'private' }

        put api("/projects/#{project3.id}", user), params: project_param

3255
        expect(response).to have_gitlab_http_status(:ok)
3256 3257 3258 3259

        expect(json_response['builds_access_level']).to eq('private')
      end

3260 3261 3262 3263 3264 3265 3266 3267 3268 3269
      it 'updates pages_access_level' do
        project_param = { pages_access_level: 'private' }

        put api("/projects/#{project3.id}", user), params: project_param

        expect(response).to have_gitlab_http_status(:ok)

        expect(json_response['pages_access_level']).to eq('private')
      end

3270 3271 3272 3273 3274
      it 'updates emails_disabled' do
        project_param = { emails_disabled: true }

        put api("/projects/#{project3.id}", user), params: project_param

3275
        expect(response).to have_gitlab_http_status(:ok)
3276 3277 3278 3279

        expect(json_response['emails_disabled']).to eq(true)
      end

3280 3281 3282 3283 3284
      it 'updates build_git_strategy' do
        project_param = { build_git_strategy: 'clone' }

        put api("/projects/#{project3.id}", user), params: project_param

3285
        expect(response).to have_gitlab_http_status(:ok)
3286 3287 3288 3289 3290 3291 3292 3293 3294

        expect(json_response['build_git_strategy']).to eq('clone')
      end

      it 'rejects to update build_git_strategy when build_git_strategy is invalid' do
        project_param = { build_git_strategy: 'invalid' }

        put api("/projects/#{project3.id}", user), params: project_param

3295
        expect(response).to have_gitlab_http_status(:bad_request)
3296 3297
      end

3298 3299 3300
      it 'updates merge_method' do
        project_param = { merge_method: 'ff' }

blackst0ne's avatar
blackst0ne committed
3301
        put api("/projects/#{project3.id}", user), params: project_param
3302

3303
        expect(response).to have_gitlab_http_status(:ok)
3304 3305 3306 3307 3308 3309 3310 3311 3312

        project_param.each_pair do |k, v|
          expect(json_response[k.to_s]).to eq(v)
        end
      end

      it 'rejects to update merge_method when merge_method is invalid' do
        project_param = { merge_method: 'invalid' }

blackst0ne's avatar
blackst0ne committed
3313
        put api("/projects/#{project3.id}", user), params: project_param
3314

3315
        expect(response).to have_gitlab_http_status(:bad_request)
3316
      end
3317

3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329
      it 'updates restrict_user_defined_variables', :aggregate_failures do
        project_param = { restrict_user_defined_variables: true }

        put api("/projects/#{project3.id}", user), params: project_param

        expect(response).to have_gitlab_http_status(:ok)

        project_param.each_pair do |k, v|
          expect(json_response[k.to_s]).to eq(v)
        end
      end

3330 3331 3332 3333 3334 3335
      it 'updates avatar' do
        project_param = {
          avatar: fixture_file_upload('spec/fixtures/banana_sample.gif',
                                      'image/gif')
        }

blackst0ne's avatar
blackst0ne committed
3336
        put api("/projects/#{project3.id}", user), params: project_param
3337

3338
        expect(response).to have_gitlab_http_status(:ok)
3339 3340 3341 3342
        expect(json_response['avatar_url']).to eq('http://localhost/uploads/'\
                                                  '-/system/project/avatar/'\
                                                  "#{project3.id}/banana_sample.gif")
      end
3343 3344 3345 3346 3347 3348

      it 'updates auto_devops_deploy_strategy' do
        project_param = { auto_devops_deploy_strategy: 'timed_incremental' }

        put api("/projects/#{project3.id}", user), params: project_param

3349
        expect(response).to have_gitlab_http_status(:ok)
3350 3351 3352 3353 3354 3355 3356 3357 3358

        expect(json_response['auto_devops_deploy_strategy']).to eq('timed_incremental')
      end

      it 'updates auto_devops_enabled' do
        project_param = { auto_devops_enabled: false }

        put api("/projects/#{project3.id}", user), params: project_param

3359
        expect(response).to have_gitlab_http_status(:ok)
3360 3361 3362

        expect(json_response['auto_devops_enabled']).to eq(false)
      end
3363 3364 3365 3366 3367 3368 3369 3370

      it 'updates topics using tag_list (deprecated)' do
        project_param = { tag_list: 'topic1' }

        put api("/projects/#{project3.id}", user), params: project_param

        expect(response).to have_gitlab_http_status(:ok)

3371
        expect(json_response['topics']).to eq(%w[topic1])
3372 3373 3374 3375 3376 3377 3378 3379 3380
      end

      it 'updates topics' do
        project_param = { topics: 'topic2' }

        put api("/projects/#{project3.id}", user), params: project_param

        expect(response).to have_gitlab_http_status(:ok)

3381
        expect(json_response['topics']).to eq(%w[topic2])
3382
      end
3383 3384

      it 'updates squash_option' do
Gary Holtz's avatar
Gary Holtz committed
3385 3386
        project3.update!(squash_option: 'always')

3387 3388
        project_param = { squash_option: "default_on" }

Gary Holtz's avatar
Gary Holtz committed
3389 3390 3391 3392
        expect { put api("/projects/#{project3.id}", user), params: project_param }
          .to change { project3.reload.squash_option }
          .from('always')
          .to('default_on')
3393 3394 3395 3396 3397 3398 3399 3400

        expect(response).to have_gitlab_http_status(:ok)
        expect(json_response['squash_option']).to eq("default_on")
      end

      it 'does not update an invalid squash_option' do
        project_param = { squash_option: "jawn" }

Gary Holtz's avatar
Gary Holtz committed
3401 3402
        expect { put api("/projects/#{project3.id}", user), params: project_param }
          .not_to change { project3.reload.squash_option }
3403 3404 3405

        expect(response).to have_gitlab_http_status(:bad_request)
      end
3406 3407
    end

3408
    context 'when authenticated as project maintainer' do
3409
      it 'updates path' do
3410
        project_param = { path: 'bar' }
blackst0ne's avatar
blackst0ne committed
3411
        put api("/projects/#{project3.id}", user4), params: project_param
3412
        expect(response).to have_gitlab_http_status(:ok)
3413
        project_param.each_pair do |k, v|
3414
          expect(json_response[k.to_s]).to eq(v)
3415 3416 3417
        end
      end

3418
      it 'updates other attributes' do
3419 3420 3421 3422
        project_param = { issues_enabled: true,
                          wiki_enabled: true,
                          snippets_enabled: true,
                          merge_requests_enabled: true,
3423
                          merge_method: 'ff',
3424
                          ci_default_git_depth: 20,
3425
                          ci_forward_deployment_enabled: false,
3426 3427
                          description: 'new description' }

blackst0ne's avatar
blackst0ne committed
3428
        put api("/projects/#{project3.id}", user4), params: project_param
3429
        expect(response).to have_gitlab_http_status(:ok)
3430
        project_param.each_pair do |k, v|
3431
          expect(json_response[k.to_s]).to eq(v)
3432 3433 3434
        end
      end

3435
      it 'does not update path to existing path' do
3436
        project_param = { path: project.path }
blackst0ne's avatar
blackst0ne committed
3437
        put api("/projects/#{project3.id}", user4), params: project_param
3438
        expect(response).to have_gitlab_http_status(:bad_request)
3439
        expect(json_response['message']['path']).to eq(['has already been taken'])
3440 3441
      end

3442
      it 'does not update name' do
3443
        project_param = { name: 'bar' }
blackst0ne's avatar
blackst0ne committed
3444
        put api("/projects/#{project3.id}", user4), params: project_param
3445
        expect(response).to have_gitlab_http_status(:forbidden)
3446 3447
      end

3448
      it 'does not update visibility_level' do
3449
        project_param = { visibility: 'public' }
blackst0ne's avatar
blackst0ne committed
3450
        put api("/projects/#{project3.id}", user4), params: project_param
3451
        expect(response).to have_gitlab_http_status(:forbidden)
3452
      end
3453 3454 3455 3456 3457

      it 'updates container_expiration_policy' do
        project_param = {
          container_expiration_policy_attributes: {
            cadence: '1month',
3458 3459
            keep_n: 1,
            name_regex_keep: 'foo.*'
3460 3461 3462 3463 3464
          }
        }

        put api("/projects/#{project3.id}", user4), params: project_param

3465
        expect(response).to have_gitlab_http_status(:ok)
3466 3467 3468

        expect(json_response['container_expiration_policy']['cadence']).to eq('1month')
        expect(json_response['container_expiration_policy']['keep_n']).to eq(1)
3469
        expect(json_response['container_expiration_policy']['name_regex_keep']).to eq('foo.*')
3470
      end
3471 3472 3473 3474 3475

      it "doesn't update container_expiration_policy with invalid regex" do
        project_param = {
          container_expiration_policy_attributes: {
            cadence: '1month',
3476
            enabled: true,
3477 3478 3479 3480 3481 3482 3483 3484 3485 3486
            keep_n: 1,
            name_regex_keep: '['
          }
        }

        put api("/projects/#{project3.id}", user4), params: project_param

        expect(response).to have_gitlab_http_status(:bad_request)
        expect(json_response['message']['container_expiration_policy.name_regex_keep']).to contain_exactly('not valid RE2 syntax: missing ]: [')
      end
3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502

      it "doesn't update container_expiration_policy with invalid keep_n" do
        project_param = {
          container_expiration_policy_attributes: {
            cadence: '1month',
            enabled: true,
            keep_n: 'not_int',
            name_regex_keep: 'foo.*'
          }
        }

        put api("/projects/#{project3.id}", user4), params: project_param

        expect(response).to have_gitlab_http_status(:bad_request)
        expect(json_response['error']).to eq('container_expiration_policy_attributes[keep_n] is invalid')
      end
3503 3504 3505
    end

    context 'when authenticated as project developer' do
3506
      it 'does not update other attributes' do
3507 3508 3509 3510 3511
        project_param = { path: 'bar',
                          issues_enabled: true,
                          wiki_enabled: true,
                          snippets_enabled: true,
                          merge_requests_enabled: true,
3512 3513
                          description: 'new description',
                          request_access_enabled: true }
blackst0ne's avatar
blackst0ne committed
3514
        put api("/projects/#{project.id}", user3), params: project_param
3515
        expect(response).to have_gitlab_http_status(:forbidden)
3516 3517
      end
    end
3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528

    context 'when updating repository storage' do
      let(:unknown_storage) { 'new-storage' }
      let(:new_project) { create(:project, :repository, namespace: user.namespace) }

      context 'as a user' do
        it 'returns 200 but does not change repository_storage' do
          expect do
            Sidekiq::Testing.fake! do
              put(api("/projects/#{new_project.id}", user), params: { repository_storage: unknown_storage, issues_enabled: false })
            end
3529
          end.not_to change(Projects::UpdateRepositoryStorageWorker.jobs, :size)
3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541

          expect(response).to have_gitlab_http_status(:ok)
          expect(json_response['issues_enabled']).to eq(false)
          expect(new_project.reload.repository.storage).to eq('default')
        end
      end

      context 'as an admin' do
        include_context 'custom session'

        let(:admin) { create(:admin) }

3542
        it 'returns 400 when repository storage is unknown' do
3543 3544
          put(api("/projects/#{new_project.id}", admin), params: { repository_storage: unknown_storage })

3545 3546
          expect(response).to have_gitlab_http_status(:bad_request)
          expect(json_response['message']['repository_storage_moves']).to eq(['is invalid'])
3547 3548 3549
        end

        it 'returns 200 when repository storage has changed' do
3550
          stub_storage_settings('test_second_storage' => { 'path' => TestEnv::SECOND_STORAGE_PATH })
3551 3552 3553 3554 3555

          expect do
            Sidekiq::Testing.fake! do
              put(api("/projects/#{new_project.id}", admin), params: { repository_storage: 'test_second_storage' })
            end
3556
          end.to change(Projects::UpdateRepositoryStorageWorker.jobs, :size).by(1)
3557 3558 3559 3560 3561

          expect(response).to have_gitlab_http_status(:ok)
        end
      end
    end
3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581

    context 'when updating service desk' do
      subject { put(api("/projects/#{project.id}", user), params: { service_desk_enabled: true }) }

      before do
        project.update!(service_desk_enabled: false)

        allow(::Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
      end

      it 'returns 200' do
        subject

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

      it 'enables the service_desk' do
        expect { subject }.to change { project.reload.service_desk_enabled }.to(true)
      end
    end
3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599

    context 'when updating keep latest artifact' do
      subject { put(api("/projects/#{project.id}", user), params: { keep_latest_artifact: true }) }

      before do
        project.update!(keep_latest_artifact: false)
      end

      it 'returns 200' do
        subject

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

      it 'enables keep_latest_artifact' do
        expect { subject }.to change { project.reload.keep_latest_artifact }.to(true)
      end
    end
3600 3601
  end

3602
  describe 'POST /projects/:id/archive' do
3603 3604
    context 'on an unarchived project' do
      it 'archives the project' do
3605
        post api("/projects/#{project.id}/archive", user)
3606

3607
        expect(response).to have_gitlab_http_status(:created)
3608 3609 3610 3611 3612 3613
        expect(json_response['archived']).to be_truthy
      end
    end

    context 'on an archived project' do
      before do
3614
        ::Projects::UpdateService.new(project, user, archived: true).execute
3615 3616 3617
      end

      it 'remains archived' do
3618
        post api("/projects/#{project.id}/archive", user)
3619

3620
        expect(response).to have_gitlab_http_status(:created)
3621 3622
        expect(json_response['archived']).to be_truthy
      end
3623
    end
3624

3625 3626
    context 'user without archiving rights to the project' do
      before do
3627
        project.add_developer(user3)
3628
      end
3629

3630 3631 3632
      it 'rejects the action' do
        post api("/projects/#{project.id}/archive", user3)

3633
        expect(response).to have_gitlab_http_status(:forbidden)
3634 3635 3636 3637
      end
    end
  end

3638
  describe 'POST /projects/:id/unarchive' do
3639 3640
    context 'on an unarchived project' do
      it 'remains unarchived' do
3641
        post api("/projects/#{project.id}/unarchive", user)
3642

3643
        expect(response).to have_gitlab_http_status(:created)
3644 3645 3646 3647 3648 3649
        expect(json_response['archived']).to be_falsey
      end
    end

    context 'on an archived project' do
      before do
3650
        ::Projects::UpdateService.new(project, user, archived: true).execute
3651 3652
      end

3653 3654
      it 'unarchives the project' do
        post api("/projects/#{project.id}/unarchive", user)
3655

3656
        expect(response).to have_gitlab_http_status(:created)
3657 3658
        expect(json_response['archived']).to be_falsey
      end
3659
    end
3660

3661 3662
    context 'user without archiving rights to the project' do
      before do
3663
        project.add_developer(user3)
3664
      end
3665

3666 3667 3668
      it 'rejects the action' do
        post api("/projects/#{project.id}/unarchive", user3)

3669
        expect(response).to have_gitlab_http_status(:forbidden)
3670 3671 3672 3673
      end
    end
  end

3674 3675 3676
  describe 'POST /projects/:id/star' do
    context 'on an unstarred project' do
      it 'stars the project' do
3677
        expect { post api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(1)
3678

3679
        expect(response).to have_gitlab_http_status(:created)
3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690
        expect(json_response['star_count']).to eq(1)
      end
    end

    context 'on a starred project' do
      before do
        user.toggle_star(project)
        project.reload
      end

      it 'does not modify the star count' do
3691
        expect { post api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count }
3692

3693
        expect(response).to have_gitlab_http_status(:not_modified)
3694 3695 3696 3697
      end
    end
  end

3698
  describe 'POST /projects/:id/unstar' do
3699 3700 3701 3702 3703 3704 3705
    context 'on a starred project' do
      before do
        user.toggle_star(project)
        project.reload
      end

      it 'unstars the project' do
3706
        expect { post api("/projects/#{project.id}/unstar", user) }.to change { project.reload.star_count }.by(-1)
3707

3708
        expect(response).to have_gitlab_http_status(:created)
3709 3710 3711 3712 3713 3714
        expect(json_response['star_count']).to eq(0)
      end
    end

    context 'on an unstarred project' do
      it 'does not modify the star count' do
3715
        expect { post api("/projects/#{project.id}/unstar", user) }.not_to change { project.reload.star_count }
3716

3717
        expect(response).to have_gitlab_http_status(:not_modified)
3718 3719 3720 3721
      end
    end
  end

3722 3723 3724 3725 3726
  describe 'GET /projects/:id/starrers' do
    shared_examples_for 'project starrers response' do
      it 'returns an array of starrers' do
        get api("/projects/#{public_project.id}/starrers", current_user)

3727
        expect(response).to have_gitlab_http_status(:ok)
3728 3729 3730 3731 3732 3733 3734
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response[0]['starred_since']).to be_present
        expect(json_response[0]['user']).to be_present
      end

      it 'returns the proper security headers' do
3735
        get api("/projects/#{public_project.id}/starrers", current_user)
3736 3737 3738 3739 3740 3741 3742 3743 3744

        expect(response).to include_security_headers
      end
    end

    let(:public_project) { create(:project, :public) }
    let(:private_user) { create(:user, private_profile: true) }

    before do
3745 3746
      user.update!(starred_projects: [public_project])
      private_user.update!(starred_projects: [public_project])
3747 3748 3749
    end

    it 'returns not_found(404) for not existing project' do
3750
      get api("/projects/#{non_existing_record_id}/starrers", user)
3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800

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

    context 'public project without user' do
      it_behaves_like 'project starrers response' do
        let(:current_user) { nil }
      end

      it 'returns only starrers with a public profile' do
        get api("/projects/#{public_project.id}/starrers", nil)

        user_ids = json_response.map { |s| s['user']['id'] }
        expect(user_ids).to include(user.id)
        expect(user_ids).not_to include(private_user.id)
      end
    end

    context 'public project with user with private profile' do
      it_behaves_like 'project starrers response' do
        let(:current_user) { private_user }
      end

      it 'returns current user with a private profile' do
        get api("/projects/#{public_project.id}/starrers", private_user)

        user_ids = json_response.map { |s| s['user']['id'] }
        expect(user_ids).to include(user.id, private_user.id)
      end
    end

    context 'private project' do
      context 'with unauthorized user' do
        it 'returns not_found for existing but unauthorized project' do
          get api("/projects/#{project3.id}/starrers", user3)

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

      context 'without user' do
        it 'returns not_found for existing but unauthorized project' do
          get api("/projects/#{project3.id}/starrers", nil)

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

3801 3802 3803 3804 3805 3806 3807
  describe 'GET /projects/:id/languages' do
    context 'with an authorized user' do
      it_behaves_like 'languages and percentages JSON response' do
        let(:project) { project3 }
      end

      it 'returns not_found(404) for not existing project' do
3808
        get api("/projects/#{non_existing_record_id}/languages", user)
3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836

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

    context 'with not authorized user' do
      it 'returns not_found for existing but unauthorized project' do
        get api("/projects/#{project3.id}/languages", user3)

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

    context 'without user' do
      let(:project_public) { create(:project, :public, :repository) }

      it_behaves_like 'languages and percentages JSON response' do
        let(:project) { project_public }
      end

      it 'returns not_found for existing but unauthorized project' do
        get api("/projects/#{project3.id}/languages", nil)

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

3837 3838
  describe 'DELETE /projects/:id' do
    context 'when authenticated as user' do
3839
      it 'removes project' do
3840
        delete api("/projects/#{project.id}", user)
3841

3842
        expect(response).to have_gitlab_http_status(:accepted)
3843
        expect(json_response['message']).to eql('202 Accepted')
3844 3845
      end

3846
      it_behaves_like '412 response' do
3847
        let(:success_status) { 202 }
3848 3849 3850
        let(:request) { api("/projects/#{project.id}", user) }
      end

3851
      it 'does not remove a project if not an owner' do
3852
        user3 = create(:user)
3853
        project.add_developer(user3)
3854
        delete api("/projects/#{project.id}", user3)
3855
        expect(response).to have_gitlab_http_status(:forbidden)
3856 3857
      end

3858
      it 'does not remove a non existing project' do
3859
        delete api("/projects/#{non_existing_record_id}", user)
3860
        expect(response).to have_gitlab_http_status(:not_found)
3861 3862
      end

3863
      it 'does not remove a project not attached to user' do
3864
        delete api("/projects/#{project.id}", user2)
3865
        expect(response).to have_gitlab_http_status(:not_found)
3866 3867 3868
      end
    end

3869
    context 'when authenticated as admin' do
3870
      it 'removes any existing project' do
3871
        delete api("/projects/#{project.id}", admin)
3872

3873
        expect(response).to have_gitlab_http_status(:accepted)
3874
        expect(json_response['message']).to eql('202 Accepted')
3875 3876
      end

3877
      it 'does not remove a non existing project' do
3878
        delete api("/projects/#{non_existing_record_id}", admin)
3879
        expect(response).to have_gitlab_http_status(:not_found)
3880
      end
3881 3882

      it_behaves_like '412 response' do
3883
        let(:success_status) { 202 }
3884 3885
        let(:request) { api("/projects/#{project.id}", admin) }
      end
3886 3887
    end
  end
3888 3889 3890 3891 3892

  describe 'POST /projects/:id/fork' do
    let(:project) do
      create(:project, :repository, creator: user, namespace: user.namespace)
    end
3893 3894 3895 3896 3897

    let(:project2) do
      create(:project, :repository, creator: user, namespace: user.namespace)
    end

3898 3899 3900
    let(:group) { create(:group, :public) }
    let(:group2) { create(:group, name: 'group2_name') }
    let(:group3) { create(:group, name: 'group3_name', parent: group2) }
3901

3902
    before do
3903 3904 3905
      group.add_guest(user2)
      group2.add_maintainer(user2)
      group3.add_owner(user2)
3906
      project.add_reporter(user2)
3907
      project2.add_reporter(user2)
3908 3909 3910 3911 3912 3913
    end

    context 'when authenticated' do
      it 'forks if user has sufficient access to project' do
        post api("/projects/#{project.id}/fork", user2)

3914
        expect(response).to have_gitlab_http_status(:created)
3915 3916 3917 3918 3919
        expect(json_response['name']).to eq(project.name)
        expect(json_response['path']).to eq(project.path)
        expect(json_response['owner']['id']).to eq(user2.id)
        expect(json_response['namespace']['id']).to eq(user2.namespace.id)
        expect(json_response['forked_from_project']['id']).to eq(project.id)
Rémy Coutable's avatar
Rémy Coutable committed
3920
        expect(json_response['import_status']).to eq('scheduled')
3921
        expect(json_response).to include("import_error")
3922 3923 3924 3925 3926
      end

      it 'forks if user is admin' do
        post api("/projects/#{project.id}/fork", admin)

3927
        expect(response).to have_gitlab_http_status(:created)
3928 3929 3930 3931 3932
        expect(json_response['name']).to eq(project.name)
        expect(json_response['path']).to eq(project.path)
        expect(json_response['owner']['id']).to eq(admin.id)
        expect(json_response['namespace']['id']).to eq(admin.namespace.id)
        expect(json_response['forked_from_project']['id']).to eq(project.id)
Rémy Coutable's avatar
Rémy Coutable committed
3933
        expect(json_response['import_status']).to eq('scheduled')
3934
        expect(json_response).to include("import_error")
3935 3936 3937 3938 3939 3940
      end

      it 'fails on missing project access for the project to fork' do
        new_user = create(:user)
        post api("/projects/#{project.id}/fork", new_user)

3941
        expect(response).to have_gitlab_http_status(:not_found)
3942 3943 3944 3945
        expect(json_response['message']).to eq('404 Project Not Found')
      end

      it 'fails if forked project exists in the user namespace' do
3946 3947 3948 3949
        new_project = create(:project, name: project.name, path: project.path)
        new_project.add_reporter(user)

        post api("/projects/#{new_project.id}/fork", user)
3950

3951
        expect(response).to have_gitlab_http_status(:conflict)
3952 3953 3954 3955 3956
        expect(json_response['message']['name']).to eq(['has already been taken'])
        expect(json_response['message']['path']).to eq(['has already been taken'])
      end

      it 'fails if project to fork from does not exist' do
3957
        post api("/projects/#{non_existing_record_id}/fork", user)
3958

3959
        expect(response).to have_gitlab_http_status(:not_found)
3960 3961 3962 3963
        expect(json_response['message']).to eq('404 Project Not Found')
      end

      it 'forks with explicit own user namespace id' do
blackst0ne's avatar
blackst0ne committed
3964
        post api("/projects/#{project.id}/fork", user2), params: { namespace: user2.namespace.id }
3965

3966
        expect(response).to have_gitlab_http_status(:created)
3967 3968 3969 3970
        expect(json_response['owner']['id']).to eq(user2.id)
      end

      it 'forks with explicit own user name as namespace' do
blackst0ne's avatar
blackst0ne committed
3971
        post api("/projects/#{project.id}/fork", user2), params: { namespace: user2.username }
3972

3973
        expect(response).to have_gitlab_http_status(:created)
3974 3975 3976 3977
        expect(json_response['owner']['id']).to eq(user2.id)
      end

      it 'forks to another user when admin' do
blackst0ne's avatar
blackst0ne committed
3978
        post api("/projects/#{project.id}/fork", admin), params: { namespace: user2.username }
3979

3980
        expect(response).to have_gitlab_http_status(:created)
3981 3982 3983 3984
        expect(json_response['owner']['id']).to eq(user2.id)
      end

      it 'fails if trying to fork to another user when not admin' do
blackst0ne's avatar
blackst0ne committed
3985
        post api("/projects/#{project.id}/fork", user2), params: { namespace: admin.namespace.id }
3986

3987
        expect(response).to have_gitlab_http_status(:not_found)
3988 3989 3990
      end

      it 'fails if trying to fork to non-existent namespace' do
3991
        post api("/projects/#{project.id}/fork", user2), params: { namespace: non_existing_record_id }
3992

3993 3994
        expect(response).to have_gitlab_http_status(:not_found)
        expect(json_response['message']).to eq('404 Namespace Not Found')
3995 3996 3997
      end

      it 'forks to owned group' do
blackst0ne's avatar
blackst0ne committed
3998
        post api("/projects/#{project.id}/fork", user2), params: { namespace: group2.name }
3999

4000
        expect(response).to have_gitlab_http_status(:created)
4001 4002 4003
        expect(json_response['namespace']['name']).to eq(group2.name)
      end

4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063
      context 'when namespace_id is specified' do
        shared_examples_for 'forking to specified namespace_id' do
          it 'forks to specified namespace_id' do
            expect(response).to have_gitlab_http_status(:created)
            expect(json_response['owner']['id']).to eq(user2.id)
            expect(json_response['namespace']['id']).to eq(user2.namespace.id)
          end
        end

        context 'and namespace_id is specified alone' do
          before do
            post api("/projects/#{project.id}/fork", user2), params: { namespace_id: user2.namespace.id }
          end

          it_behaves_like 'forking to specified namespace_id'
        end

        context 'and namespace_id and namespace are both specified' do
          before do
            post api("/projects/#{project.id}/fork", user2), params: { namespace_id: user2.namespace.id, namespace: admin.namespace.id }
          end

          it_behaves_like 'forking to specified namespace_id'
        end

        context 'and namespace_id and namespace_path are both specified' do
          before do
            post api("/projects/#{project.id}/fork", user2), params: { namespace_id: user2.namespace.id, namespace_path: admin.namespace.path }
          end

          it_behaves_like 'forking to specified namespace_id'
        end
      end

      context 'when namespace_path is specified' do
        shared_examples_for 'forking to specified namespace_path' do
          it 'forks to specified namespace_path' do
            expect(response).to have_gitlab_http_status(:created)
            expect(json_response['owner']['id']).to eq(user2.id)
            expect(json_response['namespace']['path']).to eq(user2.namespace.path)
          end
        end

        context 'and namespace_path is specified alone' do
          before do
            post api("/projects/#{project.id}/fork", user2), params: { namespace_path: user2.namespace.path }
          end

          it_behaves_like 'forking to specified namespace_path'
        end

        context 'and namespace_path and namespace are both specified' do
          before do
            post api("/projects/#{project.id}/fork", user2), params: { namespace_path: user2.namespace.path, namespace: admin.namespace.path }
          end

          it_behaves_like 'forking to specified namespace_path'
        end
      end

4064 4065
      it 'forks to owned subgroup' do
        full_path = "#{group2.path}/#{group3.path}"
blackst0ne's avatar
blackst0ne committed
4066
        post api("/projects/#{project.id}/fork", user2), params: { namespace: full_path }
4067

4068
        expect(response).to have_gitlab_http_status(:created)
4069 4070 4071 4072
        expect(json_response['namespace']['name']).to eq(group3.name)
        expect(json_response['namespace']['full_path']).to eq(full_path)
      end

4073
      it 'fails to fork to not owned group' do
blackst0ne's avatar
blackst0ne committed
4074
        post api("/projects/#{project.id}/fork", user2), params: { namespace: group.name }
4075

4076 4077
        expect(response).to have_gitlab_http_status(:not_found)
        expect(json_response['message']).to eq("404 Target Namespace Not Found")
4078 4079 4080
      end

      it 'forks to not owned group when admin' do
blackst0ne's avatar
blackst0ne committed
4081
        post api("/projects/#{project.id}/fork", admin), params: { namespace: group.name }
4082

4083
        expect(response).to have_gitlab_http_status(:created)
4084 4085
        expect(json_response['namespace']['name']).to eq(group.name)
      end
4086 4087 4088 4089

      it 'accepts a path for the target project' do
        post api("/projects/#{project.id}/fork", user2), params: { path: 'foobar' }

4090
        expect(response).to have_gitlab_http_status(:created)
4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103
        expect(json_response['name']).to eq(project.name)
        expect(json_response['path']).to eq('foobar')
        expect(json_response['owner']['id']).to eq(user2.id)
        expect(json_response['namespace']['id']).to eq(user2.namespace.id)
        expect(json_response['forked_from_project']['id']).to eq(project.id)
        expect(json_response['import_status']).to eq('scheduled')
        expect(json_response).to include("import_error")
      end

      it 'fails to fork if path is already taken' do
        post api("/projects/#{project.id}/fork", user2), params: { path: 'foobar' }
        post api("/projects/#{project2.id}/fork", user2), params: { path: 'foobar' }

4104
        expect(response).to have_gitlab_http_status(:conflict)
4105 4106 4107
        expect(json_response['message']['path']).to eq(['has already been taken'])
      end

4108 4109
      it 'accepts custom parameters for the target project' do
        post api("/projects/#{project.id}/fork", user2), params: { name: 'My Random Project', description: 'A description', visibility: 'private' }
4110

4111
        expect(response).to have_gitlab_http_status(:created)
4112 4113 4114 4115 4116
        expect(json_response['name']).to eq('My Random Project')
        expect(json_response['path']).to eq(project.path)
        expect(json_response['owner']['id']).to eq(user2.id)
        expect(json_response['namespace']['id']).to eq(user2.namespace.id)
        expect(json_response['forked_from_project']['id']).to eq(project.id)
4117 4118
        expect(json_response['description']).to eq('A description')
        expect(json_response['visibility']).to eq('private')
4119 4120 4121 4122 4123 4124 4125 4126
        expect(json_response['import_status']).to eq('scheduled')
        expect(json_response).to include("import_error")
      end

      it 'fails to fork if name is already taken' do
        post api("/projects/#{project.id}/fork", user2), params: { name: 'My Random Project' }
        post api("/projects/#{project2.id}/fork", user2), params: { name: 'My Random Project' }

4127
        expect(response).to have_gitlab_http_status(:conflict)
4128 4129
        expect(json_response['message']['name']).to eq(['has already been taken'])
      end
4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149

      it 'forks to the same namespace with alternative path and name' do
        post api("/projects/#{project.id}/fork", user), params: { path: 'path_2', name: 'name_2' }

        expect(response).to have_gitlab_http_status(:created)
        expect(json_response['name']).to eq('name_2')
        expect(json_response['path']).to eq('path_2')
        expect(json_response['owner']['id']).to eq(user.id)
        expect(json_response['namespace']['id']).to eq(user.namespace.id)
        expect(json_response['forked_from_project']['id']).to eq(project.id)
        expect(json_response['import_status']).to eq('scheduled')
      end

      it 'fails to fork to the same namespace without alternative path and name' do
        post api("/projects/#{project.id}/fork", user)

        expect(response).to have_gitlab_http_status(:conflict)
        expect(json_response['message']['path']).to eq(['has already been taken'])
        expect(json_response['message']['name']).to eq(['has already been taken'])
      end
4150 4151 4152 4153 4154 4155 4156

      it 'fails to fork with an unknown visibility level' do
        post api("/projects/#{project.id}/fork", user2), params: { visibility: 'something' }

        expect(response).to have_gitlab_http_status(:bad_request)
        expect(json_response['error']).to eq('visibility does not have a valid value')
      end
4157 4158 4159 4160 4161 4162
    end

    context 'when unauthenticated' do
      it 'returns authentication error' do
        post api("/projects/#{project.id}/fork")

4163
        expect(response).to have_gitlab_http_status(:unauthorized)
4164 4165 4166
        expect(json_response['message']).to eq('401 Unauthorized')
      end
    end
4167 4168 4169

    context 'forking disabled' do
      before do
4170 4171
        project.project_feature.update_attribute(
          :forking_access_level, ProjectFeature::DISABLED)
4172 4173 4174 4175 4176
      end

      it 'denies project to be forked' do
        post api("/projects/#{project.id}/fork", admin)

4177
        expect(response).to have_gitlab_http_status(:not_found)
4178 4179
      end
    end
4180
  end
4181 4182

  describe 'POST /projects/:id/housekeeping' do
4183
    let(:housekeeping) { Repositories::HousekeepingService.new(project) }
4184 4185

    before do
4186
      allow(Repositories::HousekeepingService).to receive(:new).with(project, :gc).and_return(housekeeping)
4187 4188 4189 4190 4191 4192 4193 4194
    end

    context 'when authenticated as owner' do
      it 'starts the housekeeping process' do
        expect(housekeeping).to receive(:execute).once

        post api("/projects/#{project.id}/housekeeping", user)

4195
        expect(response).to have_gitlab_http_status(:created)
4196 4197 4198 4199
      end

      context 'when housekeeping lease is taken' do
        it 'returns conflict' do
4200
          expect(housekeeping).to receive(:execute).once.and_raise(Repositories::HousekeepingService::LeaseTaken)
4201 4202 4203

          post api("/projects/#{project.id}/housekeeping", user)

4204
          expect(response).to have_gitlab_http_status(:conflict)
4205
          expect(json_response['message']).to match(/Somebody already triggered housekeeping for this resource/)
4206 4207 4208 4209 4210 4211
        end
      end
    end

    context 'when authenticated as developer' do
      before do
4212
        project_member
4213 4214 4215 4216 4217
      end

      it 'returns forbidden error' do
        post api("/projects/#{project.id}/housekeeping", user3)

4218
        expect(response).to have_gitlab_http_status(:forbidden)
4219 4220 4221 4222 4223 4224 4225
      end
    end

    context 'when unauthenticated' do
      it 'returns authentication error' do
        post api("/projects/#{project.id}/housekeeping")

4226
        expect(response).to have_gitlab_http_status(:unauthorized)
4227 4228 4229
      end
    end
  end
4230

4231 4232 4233 4234 4235 4236 4237
  describe 'PUT /projects/:id/transfer' do
    context 'when authenticated as owner' do
      let(:group) { create :group }

      it 'transfers the project to the new namespace' do
        group.add_owner(user)

blackst0ne's avatar
blackst0ne committed
4238
        put api("/projects/#{project.id}/transfer", user), params: { namespace: group.id }
4239

4240
        expect(response).to have_gitlab_http_status(:ok)
4241 4242 4243
      end

      it 'fails when transferring to a non owned namespace' do
blackst0ne's avatar
blackst0ne committed
4244
        put api("/projects/#{project.id}/transfer", user), params: { namespace: group.id }
4245

4246
        expect(response).to have_gitlab_http_status(:not_found)
4247 4248 4249
      end

      it 'fails when transferring to an unknown namespace' do
blackst0ne's avatar
blackst0ne committed
4250
        put api("/projects/#{project.id}/transfer", user), params: { namespace: 'unknown' }
4251

4252
        expect(response).to have_gitlab_http_status(:not_found)
4253 4254 4255 4256 4257
      end

      it 'fails on missing namespace' do
        put api("/projects/#{project.id}/transfer", user)

4258
        expect(response).to have_gitlab_http_status(:bad_request)
4259 4260
      end
    end
4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272

    context 'when authenticated as developer' do
      before do
        group.add_developer(user)
      end

      context 'target namespace allows developers to create projects' do
        let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }

        it 'fails transferring the project to the target namespace' do
          put api("/projects/#{project.id}/transfer", user), params: { namespace: group.id }

4273
          expect(response).to have_gitlab_http_status(:bad_request)
4274 4275 4276
        end
      end
    end
4277 4278
  end

4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320
  describe 'GET /projects/:id/storage' do
    context 'when unauthenticated' do
      it 'does not return project storage data' do
        get api("/projects/#{project.id}/storage")

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

    it 'returns project storage data when user is admin' do
      get api("/projects/#{project.id}/storage", create(:admin))

      expect(response).to have_gitlab_http_status(:ok)
      expect(json_response['project_id']).to eq(project.id)
      expect(json_response['disk_path']).to eq(project.repository.disk_path)
      expect(json_response['created_at']).to be_present
      expect(json_response['repository_storage']).to eq(project.repository_storage)
    end

    it 'does not return project storage data when user is not admin' do
      get api("/projects/#{project.id}/storage", user3)

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

    it 'responds with a 401 for unauthenticated users trying to access a non-existent project id' do
      expect(Project.find_by(id: non_existing_record_id)).to be_nil

      get api("/projects/#{non_existing_record_id}/storage")

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

    it 'responds with a 403 for non-admin users trying to access a non-existent project id' do
      expect(Project.find_by(id: non_existing_record_id)).to be_nil

      get api("/projects/#{non_existing_record_id}/storage", user3)

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

4321 4322 4323 4324
  it_behaves_like 'custom attributes endpoints', 'projects' do
    let(:attributable) { project }
    let(:other_attributable) { project2 }
  end
Nihad Abbasov's avatar
Nihad Abbasov committed
4325
end