projects_spec.rb 59.7 KB
Newer Older
1
# -*- coding: utf-8 -*-
Nihad Abbasov's avatar
Nihad Abbasov committed
2 3
require 'spec_helper'

4
describe API::Projects do
5
  include Gitlab::CurrentSettings
6

7 8 9
  let(:user) { create(:user) }
  let(:user2) { create(:user) }
  let(:user3) { create(:user) }
Angus MacArthur's avatar
Angus MacArthur committed
10
  let(:admin) { create(:admin) }
11 12
  let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
  let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
13
  let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
14
  let(:project_member) { create(:project_member, :developer, user: user3, project: project) }
15
  let(:user4) { create(:user) }
16 17
  let(:project3) do
    create(:project,
18
    :private,
19
    :repository,
20 21 22 23 24 25
    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
26
    builds_enabled: false,
27
    snippets_enabled: false)
28
  end
29
  let(:project_member2) do
30 31 32 33 34 35
    create(:project_member,
    user: user4,
    project: project3,
    access_level: ProjectMember::MASTER)
  end
  let(:project4) do
36
    create(:project,
37 38 39 40 41 42 43
    name: 'third_project',
    path: 'third_project',
    creator_id: user4.id,
    namespace: user4.namespace)
  end

  describe 'GET /projects' do
44 45
    shared_examples_for 'projects response' do
      it 'returns an array of projects' do
46
        get api('/projects', current_user), filter
47 48

        expect(response).to have_http_status(200)
49
        expect(response).to include_pagination_headers
50 51 52 53 54
        expect(json_response).to be_an Array
        expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id))
      end
    end

55 56 57 58 59 60 61 62 63
    shared_examples_for 'projects response without N + 1 queries' do
      it 'avoids N + 1 queries' do
        control_count = ActiveRecord::QueryRecorder.new do
          get api('/projects', current_user)
        end.count

        if defined?(additional_project)
          additional_project
        else
64
          create(:project, :public)
65 66 67 68 69 70 71 72
        end

        expect do
          get api('/projects', current_user)
        end.not_to exceed_query_limit(control_count + 8)
      end
    end

73
    let!(:public_project) { create(:project, :public, name: 'public_project') }
74 75 76 77 78 79
    before do
      project
      project2
      project3
      project4
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
80

81
    context 'when unauthenticated' do
82
      it_behaves_like 'projects response' do
83 84 85 86 87 88
        let(:filter) { { search: project.name } }
        let(:current_user) { user }
        let(:projects) { [project] }
      end

      it_behaves_like 'projects response without N + 1 queries' do
89
        let(:current_user) { nil }
90
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
91 92
    end

Markus Koller's avatar
Markus Koller committed
93
    context 'when authenticated as regular user' do
94
      it_behaves_like 'projects response' do
95
        let(:filter) { {} }
96 97
        let(:current_user) { user }
        let(:projects) { [public_project, project, project2, project3] }
Nihad Abbasov's avatar
Nihad Abbasov committed
98
      end
99

100 101 102 103 104 105
      it_behaves_like 'projects response without N + 1 queries' do
        let(:current_user) { user }
      end

      context 'when some projects are in a group' do
        before do
106
          create(:project, :public, group: create(:group))
107 108 109 110
        end

        it_behaves_like 'projects response without N + 1 queries' do
          let(:current_user) { user }
111
          let(:additional_project) { create(:project, :public, group: create(:group)) }
112 113 114
        end
      end

115
      it 'includes the project labels as the tag_list' do
116
        get api('/projects', user)
117

118
        expect(response.status).to eq 200
119
        expect(response).to include_pagination_headers
120 121
        expect(json_response).to be_an Array
        expect(json_response.first.keys).to include('tag_list')
122
      end
123

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

127
        expect(response.status).to eq 200
128
        expect(response).to include_pagination_headers
129 130 131 132
        expect(json_response).to be_an Array
        expect(json_response.first.keys).to include('open_issues_count')
      end

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

        get api('/projects', user)
137

138
        expect(response.status).to eq 200
139
        expect(response).to include_pagination_headers
140
        expect(json_response).to be_an Array
141
        expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
142 143
      end

144 145 146 147
      it "does not include statistics by default" do
        get api('/projects', user)

        expect(response).to have_http_status(200)
148
        expect(response).to include_pagination_headers
149 150
        expect(json_response).to be_an Array
        expect(json_response.first).not_to include('statistics')
151 152
      end

153 154 155 156
      it "includes statistics if requested" do
        get api('/projects', user), statistics: true

        expect(response).to have_http_status(200)
157
        expect(response).to include_pagination_headers
158 159
        expect(json_response).to be_an Array
        expect(json_response.first).to include 'statistics'
160 161
      end

162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
      context 'when external issue tracker is enabled' do
        let!(:jira_service) { create(:jira_service, project: project) }

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

          expect(response.status).to eq 200
          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)

          expect(response.status).to eq 200
          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

187
      context 'and with simple=true' do
tiagonbotelho's avatar
tiagonbotelho committed
188
        it 'returns a simplified version of all the projects' do
189 190 191 192 193 194 195 196
          expected_keys = %w(
            id description default_branch tag_list
            ssh_url_to_repo http_url_to_repo web_url
            name name_with_namespace
            path path_with_namespace
            star_count forks_count
            created_at last_activity_at
          )
197

198
          get api('/projects?simple=true', user)
tiagonbotelho's avatar
tiagonbotelho committed
199

200
          expect(response).to have_http_status(200)
201
          expect(response).to include_pagination_headers
202
          expect(json_response).to be_an Array
203
          expect(json_response.first.keys).to match_array expected_keys
204 205 206
        end
      end

207
      context 'and using search' do
208 209 210 211 212 213
        it_behaves_like 'projects response' do
          let(:filter) { { search: project.name } }
          let(:current_user) { user }
          let(:projects) { [project] }
        end
      end
214

215
      context 'and membership=true' do
216
        it_behaves_like 'projects response' do
217
          let(:filter) { { membership: true } }
218 219
          let(:current_user) { user }
          let(:projects) { [project, project2, project3] }
220 221 222
        end
      end

Josh Frye's avatar
Josh Frye committed
223
      context 'and using the visibility filter' do
224
        it 'filters based on private visibility param' do
Josh Frye's avatar
Josh Frye committed
225
          get api('/projects', user), { visibility: 'private' }
226

227
          expect(response).to have_http_status(200)
228
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
229
          expect(json_response).to be_an Array
230
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id, project2.id, project3.id)
Josh Frye's avatar
Josh Frye committed
231 232
        end

233
        it 'filters based on internal visibility param' do
234 235
          project2.update_attribute(:visibility_level, Gitlab::VisibilityLevel::INTERNAL)

Josh Frye's avatar
Josh Frye committed
236
          get api('/projects', user), { visibility: 'internal' }
237

238
          expect(response).to have_http_status(200)
239
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
240
          expect(json_response).to be_an Array
241
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project2.id)
Josh Frye's avatar
Josh Frye committed
242 243
        end

244
        it 'filters based on public visibility param' do
Josh Frye's avatar
Josh Frye committed
245
          get api('/projects', user), { visibility: 'public' }
246

247
          expect(response).to have_http_status(200)
248
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
249
          expect(json_response).to be_an Array
250
          expect(json_response.map { |p| p['id'] }).to contain_exactly(public_project.id)
Josh Frye's avatar
Josh Frye committed
251 252 253
        end
      end

254
      context 'and using sorting' do
255
        it 'returns the correct order when sorted by id' do
256
          get api('/projects', user), { order_by: 'id', sort: 'desc' }
257

258
          expect(response).to have_http_status(200)
259
          expect(response).to include_pagination_headers
260 261
          expect(json_response).to be_an Array
          expect(json_response.first['id']).to eq(project3.id)
262 263
        end
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
264

265 266 267
      context 'and with owned=true' do
        it 'returns an array of projects the user owns' do
          get api('/projects', user4), owned: true
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
268

269
          expect(response).to have_http_status(200)
270
          expect(response).to include_pagination_headers
271 272 273
          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)
274
        end
275 276
      end

277
      context 'and with starred=true' do
278
        let(:public_project) { create(:project, :public) }
279

280
        before do
281
          project_member
282 283
          user3.update_attributes(starred_projects: [project, project2, project3, public_project])
        end
Marin Jankovski's avatar
Marin Jankovski committed
284

285 286
        it 'returns the starred projects viewable by the user' do
          get api('/projects', user3), starred: true
Markus Koller's avatar
Markus Koller committed
287

288
          expect(response).to have_http_status(200)
289
          expect(response).to include_pagination_headers
290 291
          expect(json_response).to be_an Array
          expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
292
        end
293 294
      end

295
      context 'and with all query parameters' do
296 297 298 299 300
        let!(:project5) { create(:project, :public, path: 'gitlab5', namespace: create(:namespace)) }
        let!(:project6) { create(:project, :public, path: 'project6', namespace: user.namespace) }
        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') }
301

302
        before do
303
          user.update_attributes(starred_projects: [project5, project7, project8, project9])
304
        end
305

306
        context 'including owned filter' do
307
          it 'returns only projects that satisfy all query parameters' do
308
            get api('/projects', user), { visibility: 'public', owned: true, starred: true, search: 'gitlab' }
309

310 311 312 313 314 315 316
            expect(response).to have_http_status(200)
            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
317

318
        context 'including membership filter' do
319 320 321 322 323 324
          before do
            create(:project_member,
                   user: user,
                   project: project5,
                   access_level: ProjectMember::MASTER)
          end
325

326 327
          it 'returns only projects that satisfy all query parameters' do
            get api('/projects', user), { visibility: 'public', membership: true, starred: true, search: 'gitlab' }
328

329 330 331 332
            expect(response).to have_http_status(200)
            expect(response).to include_pagination_headers
            expect(json_response).to be_an Array
            expect(json_response.size).to eq(2)
333
            expect(json_response.map { |project| project['id'] }).to contain_exactly(project5.id, project7.id)
334
          end
335
        end
336 337
      end
    end
338

339
    context 'when authenticated as a different user' do
340
      it_behaves_like 'projects response' do
341
        let(:filter) { {} }
342 343 344
        let(:current_user) { user2 }
        let(:projects) { [public_project] }
      end
345 346
    end

347 348
    context 'when authenticated as admin' do
      it_behaves_like 'projects response' do
349
        let(:filter) { {} }
350 351 352
        let(:current_user) { admin }
        let(:projects) { Project.all }
      end
353 354 355
    end
  end

356 357
  describe 'POST /projects' do
    context 'maximum number of projects reached' do
358
      it 'does not create new project and respond with 403' do
359
        allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0)
360 361
        expect { post api('/projects', user2), name: 'foo' }
          .to change {Project.count}.by(0)
362
        expect(response).to have_http_status(403)
363 364 365
      end
    end

366
    it 'creates new project without path but with name and returns 201' do
367 368
      expect { post api('/projects', user), name: 'Foo Project' }
        .to change { Project.count }.by(1)
369
      expect(response).to have_http_status(201)
370 371 372 373 374 375 376 377

      project = Project.first

      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
378 379
      expect { post api('/projects', user), path: 'foo_project' }
        .to change { Project.count }.by(1)
380
      expect(response).to have_http_status(201)
381 382 383 384 385 386 387

      project = Project.first

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

388
    it 'creates new project with name and path and returns 201' do
389 390
      expect { post api('/projects', user), path: 'path-project-Foo', name: 'Foo Project' }
        .to change { Project.count }.by(1)
391 392 393 394 395
      expect(response).to have_http_status(201)

      project = Project.first

      expect(project.name).to eq('Foo Project')
396
      expect(project.path).to eq('path-project-Foo')
397 398
    end

399
    it 'creates last project before reaching project limit' do
400
      allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1)
401
      post api('/projects', user2), name: 'foo'
402
      expect(response).to have_http_status(201)
403 404
    end

405
    it 'does not create new project without name or path and returns 400' do
406
      expect { post api('/projects', user) }.not_to change { Project.count }
407
      expect(response).to have_http_status(400)
408
    end
Alex Denisov's avatar
Alex Denisov committed
409

410
    it "assigns attributes to project" do
411
      project = attributes_for(:project, {
412
        path: 'camelCasePath',
413
        issues_enabled: false,
winniehell's avatar
winniehell committed
414
        jobs_enabled: false,
415
        merge_requests_enabled: false,
416
        wiki_enabled: false,
417
        only_allow_merge_if_pipeline_succeeds: false,
418
        request_access_enabled: true,
419
        only_allow_merge_if_all_discussions_are_resolved: false,
420
        ci_config_path: 'a/custom/path'
Alex Denisov's avatar
Alex Denisov committed
421 422
      })

423
      post api('/projects', user), project
Alex Denisov's avatar
Alex Denisov committed
424

winniehell's avatar
winniehell committed
425 426
      expect(response).to have_http_status(201)

427
      project.each_pair do |k, v|
428
        next if %i[has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled].include?(k)
429
        expect(json_response[k.to_s]).to eq(v)
Alex Denisov's avatar
Alex Denisov committed
430
      end
431 432 433 434 435 436

      # 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)
437
    end
438

439
    it 'sets a project as public' do
440
      project = attributes_for(:project, visibility: 'public')
441

442
      post api('/projects', user), project
443 444

      expect(json_response['visibility']).to eq('public')
445 446
    end

447
    it 'sets a project as internal' do
448 449
      project = attributes_for(:project, visibility: 'internal')

450
      post api('/projects', user), project
451 452

      expect(json_response['visibility']).to eq('internal')
453 454
    end

455
    it 'sets a project as private' do
456 457
      project = attributes_for(:project, visibility: 'private')

458
      post api('/projects', user), project
459 460

      expect(json_response['visibility']).to eq('private')
461 462
    end

463 464 465
    it 'sets tag list to a project' do
      project = attributes_for(:project, tag_list: %w[tagFirst tagSecond])

466
      post api('/projects', user), project
467 468

      expect(json_response['tag_list']).to eq(%w[tagFirst tagSecond])
469 470
    end

471 472 473
    it 'uploads avatar for project a project' do
      project = attributes_for(:project, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif'))

474
      post api('/projects', user), project
475 476

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

480
    it 'sets a project as allowing merge even if build fails' do
481
      project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
482
      post api('/projects', user), project
483
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
484 485
    end

486 487
    it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do
      project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: true })
488
      post api('/projects', user), project
489
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
490 491
    end

492 493 494
    it 'sets a project as allowing merge even if discussions are unresolved' do
      project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false })

495
      post api('/projects', user), project
496 497

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

500 501 502
    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)

503
      post api('/projects', user), project
504 505 506 507

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

508 509 510 511 512 513 514 515
    it 'sets a project as allowing merge only if all discussions are resolved' do
      project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true })

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

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

516 517 518 519 520 521
    it 'ignores import_url when it is nil' do
      project = attributes_for(:project, { import_url: nil })

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

      expect(response).to have_http_status(201)
522 523
    end

524
    context 'when a visibility level is restricted' do
525
      let(:project_param) { attributes_for(:project, visibility: 'public') }
526

527
      before do
528
        stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
529 530
      end

531
      it 'does not allow a non-admin to use a restricted visibility level' do
532
        post api('/projects', user), project_param
Felipe Artur's avatar
Felipe Artur committed
533

534
        expect(response).to have_http_status(400)
535 536 537 538 539
        expect(json_response['message']['visibility_level'].first).to(
          match('restricted by your GitLab administrator')
        )
      end

540
      it 'allows an admin to override restricted visibility settings' do
541 542
        post api('/projects', admin), project_param

543
        expect(json_response['visibility']).to eq('public')
544 545
      end
    end
546 547
  end

vanadium23's avatar
vanadium23 committed
548
  describe 'GET /users/:user_id/projects/' do
549
    let!(:public_project) { create(:project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }
vanadium23's avatar
vanadium23 committed
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567

    it 'returns error when user not found' do
      get api('/users/9999/projects/')

      expect(response).to have_http_status(404)
      expect(json_response['message']).to eq('404 User Not Found')
    end

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

      expect(response).to have_http_status(200)
      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
  end

568
  describe 'POST /projects/user/:id' do
569 570 571
    before do
      expect(project).to be_persisted
    end
Angus MacArthur's avatar
Angus MacArthur committed
572

573 574
    it 'creates new project without path but with name and return 201' do
      expect { post api("/projects/user/#{user.id}", admin), name: 'Foo Project' }.to change {Project.count}.by(1)
575
      expect(response).to have_http_status(201)
576 577 578 579 580 581 582 583

      project = Project.first

      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
584 585
      expect { post api("/projects/user/#{user.id}", admin), path: 'path-project-Foo', name: 'Foo Project' }
        .to change { Project.count }.by(1)
586 587 588 589 590 591
      expect(response).to have_http_status(201)

      project = Project.first

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

594
    it 'responds with 400 on failure and not project' do
595 596
      expect { post api("/projects/user/#{user.id}", admin) }
        .not_to change { Project.count }
597

598
      expect(response).to have_http_status(400)
Robert Schilling's avatar
Robert Schilling committed
599
      expect(json_response['error']).to eq('name is missing')
Angus MacArthur's avatar
Angus MacArthur committed
600 601
    end

602
    it 'assigns attributes to project' do
Angus MacArthur's avatar
Angus MacArthur committed
603
      project = attributes_for(:project, {
604 605
        issues_enabled: false,
        merge_requests_enabled: false,
606 607
        wiki_enabled: false,
        request_access_enabled: true
Angus MacArthur's avatar
Angus MacArthur committed
608 609 610 611
      })

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

Robert Schilling's avatar
Robert Schilling committed
612
      expect(response).to have_http_status(201)
613
      project.each_pair do |k, v|
614
        next if %i[has_external_issue_tracker path].include?(k)
615
        expect(json_response[k.to_s]).to eq(v)
Angus MacArthur's avatar
Angus MacArthur committed
616 617
      end
    end
618

619
    it 'sets a project as public' do
620
      project = attributes_for(:project, visibility: 'public')
621

622
      post api("/projects/user/#{user.id}", admin), project
Robert Schilling's avatar
Robert Schilling committed
623 624

      expect(response).to have_http_status(201)
625
      expect(json_response['visibility']).to eq('public')
626
    end
627

628
    it 'sets a project as internal' do
629 630
      project = attributes_for(:project, visibility: 'internal')

631
      post api("/projects/user/#{user.id}", admin), project
Robert Schilling's avatar
Robert Schilling committed
632 633

      expect(response).to have_http_status(201)
634
      expect(json_response['visibility']).to eq('internal')
635 636
    end

637
    it 'sets a project as private' do
638 639
      project = attributes_for(:project, visibility: 'private')

640
      post api("/projects/user/#{user.id}", admin), project
641 642

      expect(json_response['visibility']).to eq('private')
643
    end
644

645
    it 'sets a project as allowing merge even if build fails' do
646
      project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
647
      post api("/projects/user/#{user.id}", admin), project
648
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
649 650
    end

651 652
    it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do
      project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: true })
653
      post api("/projects/user/#{user.id}", admin), project
654
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
655
    end
656

657 658 659
    it 'sets a project as allowing merge even if discussions are unresolved' do
      project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: false })

660
      post api("/projects/user/#{user.id}", admin), project
661 662

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

665 666 667
    it 'sets a project as allowing merge only if all discussions are resolved' do
      project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true })

668
      post api("/projects/user/#{user.id}", admin), project
669 670

      expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
671
    end
Angus MacArthur's avatar
Angus MacArthur committed
672 673
  end

Douwe Maan's avatar
Douwe Maan committed
674
  describe "POST /projects/:id/uploads" do
675 676 677
    before do
      project
    end
Douwe Maan's avatar
Douwe Maan committed
678 679 680 681

    it "uploads the file and returns its info" do
      post api("/projects/#{project.id}/uploads", user), file: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")

682
      expect(response).to have_http_status(201)
Douwe Maan's avatar
Douwe Maan committed
683 684 685 686 687 688
      expect(json_response['alt']).to eq("dk")
      expect(json_response['url']).to start_with("/uploads/")
      expect(json_response['url']).to end_with("/dk.png")
    end
  end

689
  describe 'GET /projects/:id' do
690 691
    context 'when unauthenticated' do
      it 'returns the public projects' do
692
        public_project = create(:project, :public)
693

694
        get api("/projects/#{public_project.id}")
695

696 697 698
        expect(response).to have_http_status(200)
        expect(json_response['id']).to eq(public_project.id)
        expect(json_response['description']).to eq(public_project.description)
699
        expect(json_response['default_branch']).to eq(public_project.default_branch)
700 701
        expect(json_response.keys).not_to include('permissions')
      end
702
    end
703

704 705 706 707 708
    context 'when authenticated' do
      before do
        project
        project_member
      end
709

710 711 712
      it 'returns a project by id' do
        group = create(:group)
        link = create(:project_group_link, project: project, group: group)
713

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

716 717 718 719 720 721
        expect(response).to have_http_status(200)
        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)
        expect(json_response['tag_list']).to be_an Array
        expect(json_response['archived']).to be_falsey
722
        expect(json_response['visibility']).to be_present
723 724 725 726 727 728 729 730 731 732
        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
        expect(json_response['owner']).to be_a Hash
        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
        expect(json_response['wiki_enabled']).to be_present
Toon Claes's avatar
Toon Claes committed
733
        expect(json_response['jobs_enabled']).to be_present
734 735 736 737 738 739 740
        expect(json_response['snippets_enabled']).to be_present
        expect(json_response['container_registry_enabled']).to be_present
        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
741 742
        expect(json_response['import_status']).to be_present
        expect(json_response).to include("import_error")
743 744 745
        expect(json_response['avatar_url']).to be_nil
        expect(json_response['star_count']).to be_present
        expect(json_response['forks_count']).to be_present
Toon Claes's avatar
Toon Claes committed
746
        expect(json_response['public_jobs']).to be_present
747
        expect(json_response['ci_config_path']).to be_nil
748 749 750 751 752
        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)
753
        expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
754 755
        expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
      end
756

757 758 759 760 761
      it 'returns a project by path name' do
        get api("/projects/#{project.id}", user)
        expect(response).to have_http_status(200)
        expect(json_response['name']).to eq(project.name)
      end
762

763 764 765 766 767
      it 'returns a 404 error if not found' do
        get api('/projects/42', user)
        expect(response).to have_http_status(404)
        expect(json_response['message']).to eq('404 Project Not Found')
      end
768

769 770 771 772
      it 'returns a 404 error if user is not a member' do
        other_user = create(:user)
        get api("/projects/#{project.id}", other_user)
        expect(response).to have_http_status(404)
773
      end
774

775 776
      it 'handles users with dots' do
        dot_user = create(:user, username: 'dot.user')
777
        project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace)
778

779
        get api("/projects/#{CGI.escape(project.full_path)}", dot_user)
780 781
        expect(response).to have_http_status(200)
        expect(json_response['name']).to eq(project.name)
782 783
      end

784 785
      it 'exposes namespace fields' do
        get api("/projects/#{project.id}", user)
786

787 788 789 790 791 792
        expect(response).to have_http_status(200)
        expect(json_response['namespace']).to eq({
          'id' => user.namespace.id,
          'name' => user.namespace.name,
          'path' => user.namespace.path,
          'kind' => user.namespace.kind,
793 794
          'full_path' => user.namespace.full_path,
          'parent_id' => nil
795
        })
796 797
      end

798 799
      it "does not include statistics by default" do
        get api("/projects/#{project.id}", user)
800

801 802 803
        expect(response).to have_http_status(200)
        expect(json_response).not_to include 'statistics'
      end
804

805 806
      it "includes statistics if requested" do
        get api("/projects/#{project.id}", user), statistics: true
807

808 809
        expect(response).to have_http_status(200)
        expect(json_response).to include 'statistics'
810
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
811

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

815 816
        expect(response).to have_http_status(200)
        expect(json_response).to include("import_error")
817 818
      end

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

822 823 824
        expect(response).to have_http_status(200)
        expect(json_response).not_to include("import_error")
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
825

826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841
      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
842
          project = create(:project, :public,
843 844 845 846 847 848 849 850 851 852 853 854 855 856 857
                           :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

858 859
      describe 'permissions' do
        context 'all projects' do
860 861 862
          before do
            project.team << [user, :master]
          end
863 864 865 866 867

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

            expect(response).to have_http_status(200)
868 869
            expect(json_response.first['permissions']['project_access']['access_level'])
            .to eq(Gitlab::Access::MASTER)
870 871 872 873 874 875 876 877 878 879
            expect(json_response.first['permissions']['group_access']).to be_nil
          end
        end

        context 'personal project' do
          it 'sets project access and returns 200' do
            project.team << [user, :master]
            get api("/projects/#{project.id}", user)

            expect(response).to have_http_status(200)
880 881
            expect(json_response['permissions']['project_access']['access_level'])
            .to eq(Gitlab::Access::MASTER)
882 883
            expect(json_response['permissions']['group_access']).to be_nil
          end
884
        end
885

886
        context 'group project' do
887
          let(:project2) { create(:project, group: create(:group)) }
888

889 890 891
          before do
            project2.group.add_owner(user)
          end
892

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

896 897
            expect(response).to have_http_status(200)
            expect(json_response['permissions']['project_access']).to be_nil
898 899
            expect(json_response['permissions']['group_access']['access_level'])
            .to eq(Gitlab::Access::OWNER)
900
          end
901
        end
902
      end
903
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
904
  end
905

Rémy Coutable's avatar