projects_spec.rb 62.2 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, namespace: user.namespace) }
  let(:project2) { create(:project, path: 'project2', 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

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
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
        resolve_outdated_diff_discussions: false,
418
        only_allow_merge_if_pipeline_succeeds: false,
419
        request_access_enabled: true,
420
        only_allow_merge_if_all_discussions_are_resolved: false,
421
        ci_config_path: 'a/custom/path'
Alex Denisov's avatar
Alex Denisov committed
422 423
      })

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

472 473 474
    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'))

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

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

481
    it 'sets a project as allowing outdated diff discussions to automatically resolve' do
Sean McGivern's avatar
Sean McGivern committed
482 483
      project = attributes_for(:project, resolve_outdated_diff_discussions: false)

484
      post api('/projects', user), project
Sean McGivern's avatar
Sean McGivern committed
485

486
      expect(json_response['resolve_outdated_diff_discussions']).to be_falsey
487 488
    end

489
    it 'sets a project as allowing outdated diff discussions to automatically resolve if resolve_outdated_diff_discussions' do
Sean McGivern's avatar
Sean McGivern committed
490 491
      project = attributes_for(:project, resolve_outdated_diff_discussions: true)

492
      post api('/projects', user), project
Sean McGivern's avatar
Sean McGivern committed
493

494
      expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
495 496
    end

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

500
      post api('/projects', user), project
Sean McGivern's avatar
Sean McGivern committed
501

502
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
503 504
    end

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

508
      post api('/projects', user), project
Sean McGivern's avatar
Sean McGivern committed
509

510
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
511 512
    end

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

516
      post api('/projects', user), project
517 518

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

521 522 523
    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)

524
      post api('/projects', user), project
525 526 527 528

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

529
    it 'sets a project as allowing merge only if all discussions are resolved' do
Sean McGivern's avatar
Sean McGivern committed
530
      project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: true)
531 532 533 534 535 536

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

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

537
    it 'ignores import_url when it is nil' do
Sean McGivern's avatar
Sean McGivern committed
538
      project = attributes_for(:project, import_url: nil)
539 540 541 542

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

      expect(response).to have_http_status(201)
543 544
    end

545
    context 'when a visibility level is restricted' do
546
      let(:project_param) { attributes_for(:project, visibility: 'public') }
547

548
      before do
549
        stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
550 551
      end

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

555
        expect(response).to have_http_status(400)
556 557 558 559 560
        expect(json_response['message']['visibility_level'].first).to(
          match('restricted by your GitLab administrator')
        )
      end

561
      it 'allows an admin to override restricted visibility settings' do
562 563
        post api('/projects', admin), project_param

564
        expect(json_response['visibility']).to eq('public')
565 566
      end
    end
567 568
  end

vanadium23's avatar
vanadium23 committed
569
  describe 'GET /users/:user_id/projects/' do
570
    let!(:public_project) { create(:project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }
vanadium23's avatar
vanadium23 committed
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588

    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

589
  describe 'POST /projects/user/:id' do
590 591 592
    before do
      expect(project).to be_persisted
    end
Angus MacArthur's avatar
Angus MacArthur committed
593

594 595
    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)
596
      expect(response).to have_http_status(201)
597 598 599 600 601 602 603 604

      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
605 606
      expect { post api("/projects/user/#{user.id}", admin), path: 'path-project-Foo', name: 'Foo Project' }
        .to change { Project.count }.by(1)
607 608 609 610 611 612
      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
613 614
    end

615
    it 'responds with 400 on failure and not project' do
616 617
      expect { post api("/projects/user/#{user.id}", admin) }
        .not_to change { Project.count }
618

619
      expect(response).to have_http_status(400)
620
      expect(json_response['error']).to eq('name is missing')
Angus MacArthur's avatar
Angus MacArthur committed
621 622
    end

623
    it 'assigns attributes to project' do
Angus MacArthur's avatar
Angus MacArthur committed
624
      project = attributes_for(:project, {
625 626
        issues_enabled: false,
        merge_requests_enabled: false,
627 628
        wiki_enabled: false,
        request_access_enabled: true
Angus MacArthur's avatar
Angus MacArthur committed
629 630 631 632
      })

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

633
      expect(response).to have_http_status(201)
634
      project.each_pair do |k, v|
635
        next if %i[has_external_issue_tracker path].include?(k)
636
        expect(json_response[k.to_s]).to eq(v)
Angus MacArthur's avatar
Angus MacArthur committed
637 638
      end
    end
639

640
    it 'sets a project as public' do
641
      project = attributes_for(:project, visibility: 'public')
642

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

      expect(response).to have_http_status(201)
646
      expect(json_response['visibility']).to eq('public')
647
    end
648

649
    it 'sets a project as internal' do
650 651
      project = attributes_for(:project, visibility: 'internal')

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

      expect(response).to have_http_status(201)
655
      expect(json_response['visibility']).to eq('internal')
656 657
    end

658
    it 'sets a project as private' do
659 660
      project = attributes_for(:project, visibility: 'private')

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

      expect(json_response['visibility']).to eq('private')
664
    end
665

666
    it 'sets a project as allowing outdated diff discussions to automatically resolve' do
Sean McGivern's avatar
Sean McGivern committed
667
      project = attributes_for(:project, resolve_outdated_diff_discussions: false)
668 669 670

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

671
      expect(json_response['resolve_outdated_diff_discussions']).to be_falsey
672 673
    end

Sean McGivern's avatar
Sean McGivern committed
674 675
    it 'sets a project as allowing outdated diff discussions to automatically resolve' do
      project = attributes_for(:project, resolve_outdated_diff_discussions: true)
676 677 678

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

679
      expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
680 681
    end

682
    it 'sets a project as allowing merge even if build fails' do
Sean McGivern's avatar
Sean McGivern committed
683
      project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: false)
684
      post api("/projects/user/#{user.id}", admin), project
685
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
686 687
    end

Sean McGivern's avatar
Sean McGivern committed
688 689
    it 'sets a project as allowing merge only if pipeline succeeds' do
      project = attributes_for(:project, only_allow_merge_if_pipeline_succeeds: true)
690
      post api("/projects/user/#{user.id}", admin), project
691
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
692
    end
693

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

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

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

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

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

      expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
708
    end
Angus MacArthur's avatar
Angus MacArthur committed
709 710
  end

711
  describe "POST /projects/:id/uploads" do
712 713 714
    before do
      project
    end
715 716 717 718

    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")

719
      expect(response).to have_http_status(201)
720 721 722 723 724 725
      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

726
  describe 'GET /projects/:id' do
727 728
    context 'when unauthenticated' do
      it 'returns the public projects' do
729
        public_project = create(:project, :public)
730

731
        get api("/projects/#{public_project.id}")
732

733 734 735
        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)
736
        expect(json_response['default_branch']).to eq(public_project.default_branch)
737 738
        expect(json_response.keys).not_to include('permissions')
      end
739
    end
740

741 742 743 744 745
    context 'when authenticated' do
      before do
        project
        project_member
      end
746

747 748 749
      it 'returns a project by id' do
        group = create(:group)
        link = create(:project_group_link, project: project, group: group)
750

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

753 754 755 756 757 758
        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
759
        expect(json_response['visibility']).to be_present
760 761 762 763 764 765 766 767 768 769
        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
770
        expect(json_response['jobs_enabled']).to be_present
771
        expect(json_response['snippets_enabled']).to be_present
772
        expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions)
773 774 775 776 777 778
        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
779 780
        expect(json_response['import_status']).to be_present
        expect(json_response).to include("import_error")
781 782 783
        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
784
        expect(json_response['public_jobs']).to be_present
785
        expect(json_response['ci_config_path']).to be_nil
786 787 788 789 790
        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)
791
        expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
792 793
        expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
      end
794

795 796 797 798 799
      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
800

801 802 803 804 805
      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
806

807 808 809 810
      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)
811
      end
812

813 814
      it 'handles users with dots' do
        dot_user = create(:user, username: 'dot.user')
815
        project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace)
816

817
        get api("/projects/#{CGI.escape(project.full_path)}", dot_user)
818 819
        expect(response).to have_http_status(200)
        expect(json_response['name']).to eq(project.name)
820 821
      end

822 823
      it 'exposes namespace fields' do
        get api("/projects/#{project.id}", user)
824

825 826 827 828 829 830
        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,
831 832
          'full_path' => user.namespace.full_path,
          'parent_id' => nil
833
        })
834 835
      end

836 837
      it "does not include statistics by default" do
        get api("/projects/#{project.id}", user)
838

839 840 841
        expect(response).to have_http_status(200)
        expect(json_response).not_to include 'statistics'
      end
842

843 844
      it "includes statistics if requested" do
        get api("/projects/#{project.id}", user), statistics: true
845

846 847
        expect(response).to have_http_status(200)
        expect(json_response).to include 'statistics'
848
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
849

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

853 854
        expect(response).to have_http_status(200)
        expect(json_response).to include("import_error")
855 856
      end

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

860 861 862
        expect(response).to have_http_status(200)
        expect(json_response).not_to include("import_error")
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
863

864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
      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
880
          project = create(:project, :public,
881 882 883 884 885 886 887 888 889 890 891 892 893 894 895
                           :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

896 897
      describe 'permissions' do
        context 'all projects' do
898 899 900
          before do
            project.team << [user, :master]
          end
901 902 903 904 905

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

            expect(response).to have_http_status(200)
906 907
            expect(json_response.first['permissions']['project_access']['access_level'])
            .to eq(Gitlab::Access::MASTER)
908 909 910 911 912 913 914 915 916 917
            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)
918 919
            expect(json_response['permissions']['project_access']['access_level'])
            .to eq(Gitlab::Access::MASTER)
920 921
            expect(json_response['permissions']['group_access']).to be_nil
          end
922
        end
923

924
        context 'group project' do
925
          let(:project2) { create(:project, group: create(:group)) }
926

927 928 929
          before do
            project2.group.add_owner(user)
          end
930

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

934 935
            expect(response).to have_http_status(200)
            expect(json_response['permissions']['project_access']).to be_nil
936 937
            expect(json_response['permissions']['group_access']['access_level'])
            .to eq(Gitlab::Access::OWNER)
938
          end
939
        end
940
      end
941
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
942
  end
943

944 945 946 947
  describe 'GET /projects/:id/users' do
    shared_examples_for 'project users response' do
      it 'returns the project users' do
        get api("/projects/#{project.id}/users", current_user)
948

949 950
        user = project.namespace.owner

951
        expect(response).to have_http_status(200)
952
        expect(response).to include_pagination_headers
953 954 955 956
        expect(json_response).to be_an Array
        expect(json_response.size).to eq(1)

        first_user = json_response.first
957 958
        expect(first_user['username']).to eq(user.username)
        expect(first_user['name']).to eq(user.name)
959
        expect(first_user.keys).to contain_exactly(*%w[name username id state avatar_url web_url])
960
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
961 962
    end

963 964
    context 'when unauthenticated' do
      it_behaves_like 'project users response' do
965
        let(:project) { create(:project, :public) }
966 967
        let(:current_user) { nil }
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
968 969
    end

970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990
    context 'when authenticated' do
      context 'valid request' do
        it_behaves_like 'project users response' do
          let(:current_user) { user }
        end
      end

      it 'returns a 404 error if not found' do
        get api('/projects/42/users', user)

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

        expect(response).to have_http_status(404)
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
991 992 993
    end
  end

994
  describe 'GET /projects/:id/snippets' do
995 996 997
    before do
      snippet
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
998

999
    it 'returns an array of project snippets' do
1000
      get api("/projects/#{project.id}/snippets", user)
1001

1002
      expect(response).to have_http_status(200)
1003
      expect(response).to include_pagination_headers
1004 1005
      expect(json_response).to be_an Array
      expect(json_response.first['title']).to eq(snippet.title)
1006 1007 1008
    end
  end

1009
  describe 'GET /projects/:id/snippets/:snippet_id' do
1010
    it 'returns a project snippet' do
1011
      get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
1012
      expect(response).to have_http_status(200)
1013
      expect(json_response['title']).to eq(snippet.title)
Nihad Abbasov's avatar
Nihad Abbasov committed
1014
    end
1015

1016
    it 'returns a 404 error if snippet id not found' do
1017
      get api("/projects/#{project.id}/snippets/1234", user)
1018
      expect(response).to have_http_status(404)
1019
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
1020 1021
  end

1022
  describe 'POST /projects/:id/snippets' do
1023
    it 'creates a new project snippet' do
1024
      post api("/projects/#{project.id}/snippets", user),
1025
        title: 'api test', file_name: 'sample.rb', code: 'test', visibility: 'private'
1026
      expect(response).to have_http_status(201)
1027
      expect(json_response['title']).to eq('api test')
Nihad Abbasov's avatar
Nihad Abbasov committed
1028
    end
1029

1030
    it 'returns a 400 error if invalid snippet is given' do
1031 1032
      post api("/projects/#{project.id}/snippets", user)
      expect(status).to eq(400)
1033
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
1034 1035
  end

1036
  describe 'PUT /projects/:id/snippets/:snippet_id' do
1037
    it 'updates an existing project snippet' do
1038
      put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
1039
        code: 'updated code'
1040
      expect(response).to have_http_status(200)
1041 1042
      expect(json_response['title']).to eq('example')
      expect(snippet.reload.content).to eq('updated code')
1043
    end
1044

1045
    it 'updates an existing project snippet with new title' do
1046
      put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
1047
        title: 'other api test'
1048
      expect(response).to have_http_status(200)
1049
      expect(json_response['title']).to eq('other api test')
1050
    end
1051 1052
  end

1053
  describe 'DELETE /projects/:id/snippets/:snippet_id' do
1054 1055 1056
    before do
      snippet
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
1057

1058
    it 'deletes existing project snippet' do
1059
      expect do
1060
        delete api("/projects/#{project.id}/snippets/#{snippet.id}", user)
1061 1062

        expect(response).to have_http_status(204)
1063
      end.to change { Snippet.count }.by(-1)
1064 1065
    end

1066
    it 'returns 404 when deleting unknown snippet id' do
1067
      delete api("/projects/#{project.id}/snippets/1234", user)
1068
      expect(response).to have_http_status(404)
Nihad Abbasov's avatar
Nihad Abbasov committed
1069
    end
1070 1071 1072 1073

    it_behaves_like '412 response' do
      let(:request) { api("/projects/#{project.id}/snippets/#{snippet.id}", user) }
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
1074
  end
1075

1076
  describe 'GET /projects/:id/snippets/:snippet_id/raw' do
1077
    it 'gets a raw project snippet' do
1078
      get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user)
1079
      expect(response).to have_http_status(200)
1080
    end
1081

1082
    it 'returns a 404 error if raw project snippet not found' do
1083
      get api("/projects/#{project.id}/snippets/5555/raw", user)
1084
      expect(response).to have_http_status(404)
1085
    end
1086
  end
1087

1088
  describe 'fork management' do
1089 1090
    let(:project_fork_target) { create(:project) }
    let(:project_fork_source) { create(:project, :public) }
1091

1092
    describe 'POST /projects/:id/fork/:forked_from_id' do
1093
      let(:new_project_fork_source) { create(:project, :public) }
1094

1095
      it "is not available for non admin users" do
1096
        post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
1097
        expect(response).to have_http_status(403)
1098 1099
      end

1100
      it 'allows project to be forked from an existing project' do
1101
        expect(project_fork_target.forked?).not_to be_truthy
1102
        post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
1103
        expect(response).to have_http_status(201)
1104
        project_fork_target.reload
1105 1106 1107
        expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
        expect(project_fork_target.forked_project_link).not_to be_nil
        expect(project_fork_target.forked?).to be_truthy
1108 1109
      end

1110 1111 1112 1113 1114 1115 1116 1117
      it 'refreshes the forks count cachce' do
        expect(project_fork_source.forks_count).to be_zero

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

        expect(project_fork_source.forks_count).to eq(1)
      end

1118
      it 'fails if forked_from project which does not exist' do
1119
        post api("/projects/#{project_fork_target.id}/fork/9999", admin)
1120
        expect(response).to have_http_status(404)
1121 1122
      end

1123
      it 'fails with 409 if already forked' do
1124 1125
        post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
        project_fork_target.reload
1126
        expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
1127
        post api("/projects/#{project_fork_target.id}/fork/#{new_project_fork_source.id}", admin)
1128
        expect(response).to have_http_status(409)
1129
        project_fork_target.reload
1130 1131
        expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
        expect(project_fork_target.forked?).to be_truthy
1132 1133 1134
      end
    end

1135
    describe 'DELETE /projects/:id/fork' do
1136
      it "is not visible to users outside group" do
1137
        delete api("/projects/#{project_fork_target.id}/fork", user)
1138
        expect(response).to have_http_status(404)
1139 1140
      end

1141
      context 'when users belong to project group' do
1142
        let(:project_fork_target) { create(:project, group: create(:group)) }
1143

1144 1145 1146 1147 1148
        before do
          project_fork_target.group.add_owner user
          project_fork_target.group.add_developer user2
        end

1149 1150 1151 1152 1153 1154 1155
        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
            expect(project_fork_target.forked_from_project).not_to be_nil
            expect(project_fork_target.forked?).to be_truthy
          end
1156

1157 1158
          it 'makes forked project unforked' do
            delete api("/projects/#{project_fork_target.id}/fork", admin)
1159

1160 1161 1162 1163 1164
            expect(response).to have_http_status(204)
            project_fork_target.reload
            expect(project_fork_target.forked_from_project).to be_nil
            expect(project_fork_target.forked?).not_to be_truthy
          end
1165

1166 1167 1168 1169 1170 1171 1172 1173
          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)
          expect(response).to have_http_status(403)
1174 1175
        end

1176
        it 'is idempotent if not forked' do
1177 1178
          expect(project_fork_target.forked_from_project).to be_nil
          delete api("/projects/#{project_fork_target.id}/fork", admin)
1179
          expect(response).to have_http_status(304)
1180 1181
          expect(project_fork_target.reload.forked_from_project).to be_nil
        end
1182 1183 1184
      end
    end
  end
1185

1186 1187 1188
  describe "POST /projects/:id/share" do
    let(:group) { create(:group) }

1189
    it "shares project with group" do
1190 1191
      expires_at = 10.days.from_now.to_date

1192
      expect do
1193
        post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at
1194 1195
      end.to change { ProjectGroupLink.count }.by(1)

1196
      expect(response).to have_http_status(201)
1197 1198 1199
      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)
1200 1201
    end

1202
    it "returns a 400 error when group id is not given" do
1203
      post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER
1204
      expect(response).to have_http_status(400)
1205 1206
    end

1207
    it "returns a 400 error when access level is not given" do
1208
      post api("/projects/#{project.id}/share", user), group_id: group.id
1209
      expect(response).to have_http_status(400)
1210 1211
    end

1212
    it "returns a 400 error when sharing is disabled" do
1213 1214
      project.namespace.update(share_with_group_lock: true)
      post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER
1215
      expect(response).to have_http_status(400)
1216 1217
    end

1218 1219 1220 1221 1222
    it 'returns a 404 error when user cannot read group' do
      private_group = create(:group, :private)

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

1223
      expect(response).to have_http_status(404)
1224 1225
    end

1226 1227 1228
    it 'returns a 404 error when group does not exist' do
      post api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER

1229
      expect(response).to have_http_status(404)
1230 1231
    end

1232
    it "returns a 400 error when wrong params passed" do
1233
      post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234
1234 1235 1236

      expect(response).to have_http_status(400)
      expect(json_response['error']).to eq 'group_access does not have a valid value'
1237 1238 1239
    end
  end

1240
  describe 'DELETE /projects/:id/share/:group_id' do
1241 1242 1243 1244 1245 1246 1247 1248 1249
    context 'for a valid group' do
      let(:group) { create(:group, :public) }

      before do
        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)
1250

1251 1252 1253
        expect(response).to have_http_status(204)
        expect(project.project_group_links).to be_empty
      end
1254

1255 1256 1257
      it_behaves_like '412 response' do
        let(:request) { api("/projects/#{project.id}/share/#{group.id}", user) }
      end
1258 1259
    end

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

      expect(response).to have_http_status(400)
1264 1265
    end

1266 1267 1268 1269 1270 1271 1272 1273 1274 1275
    it 'returns a 404 error when group link does not exist' do
      delete api("/projects/#{project.id}/share/1234", user)

      expect(response).to have_http_status(404)
    end

    it 'returns a 404 error when project does not exist' do
      delete api("/projects/123/share/1234", user)

      expect(response).to have_http_status(404)
1276 1277
    end
  end
1278

1279
  describe 'PUT /projects/:id' do
1280 1281 1282 1283 1284 1285 1286 1287 1288 1289
    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
1290

1291 1292
    it 'returns 400 when nothing sent' do
      project_param = {}
winniehell's avatar
winniehell committed
1293

1294
      put api("/projects/#{project.id}", user), project_param
winniehell's avatar
winniehell committed
1295

1296 1297 1298
      expect(response).to have_http_status(400)
      expect(json_response['error']).to match('at least one parameter must be provided')
    end
1299 1300

    context 'when unauthenticated' do
1301
      it 'returns authentication error' do
1302
        project_param = { name: 'bar' }
winniehell's avatar
winniehell committed
1303

1304
        put api("/projects/#{project.id}"), project_param
winniehell's avatar
winniehell committed
1305

1306
        expect(response).to have_http_status(401)
1307 1308 1309 1310
      end
    end

    context 'when authenticated as project owner' do
1311
      it 'updates name' do
1312
        project_param = { name: 'bar' }
winniehell's avatar
winniehell committed
1313

1314
        put api("/projects/#{project.id}", user), project_param
winniehell's avatar
winniehell committed
1315

1316
        expect(response).to have_http_status(200)
winniehell's avatar
winniehell committed
1317

1318
        project_param.each_pair do |k, v|
1319
          expect(json_response[k.to_s]).to eq(v)
1320 1321 1322
        end
      end

1323
      it 'updates visibility_level' do
1324
        project_param = { visibility: 'public' }
winniehell's avatar
winniehell committed
1325

1326
        put api("/projects/#{project3.id}", user), project_param
winniehell's avatar
winniehell committed
1327

1328
        expect(response).to have_http_status(200)
winniehell's avatar
winniehell committed
1329

1330
        project_param.each_pair do |k, v|
1331
          expect(json_response[k.to_s]).to eq(v)
1332 1333 1334
        end
      end

1335
      it 'updates visibility_level from public to private' do
1336
        project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC })
1337
        project_param = { visibility: 'private' }
1338 1339

        put api("/projects/#{project3.id}", user), project_param
winniehell's avatar
winniehell committed
1340

1341
        expect(response).to have_http_status(200)
winniehell's avatar
winniehell committed
1342

1343 1344 1345
        project_param.each_pair do |k, v|
          expect(json_response[k.to_s]).to eq(v)
        end
winniehell's avatar
winniehell committed
1346

1347
        expect(json_response['visibility']).to eq('private')
1348 1349
      end

1350
      it 'does not update name to existing name' do
1351
        project_param = { name: project3.name }
winniehell's avatar
winniehell committed
1352

1353
        put api("/projects/#{project.id}", user), project_param
winniehell's avatar
winniehell committed
1354

1355
        expect(response).to have_http_status(400)
1356
        expect(json_response['message']['name']).to eq(['has already been taken'])
1357 1358
      end

1359 1360 1361 1362 1363 1364 1365 1366 1367
      it 'updates request_access_enabled' do
        project_param = { request_access_enabled: false }

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

        expect(response).to have_http_status(200)
        expect(json_response['request_access_enabled']).to eq(false)
      end

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

1371
        put api("/projects/#{project3.id}", user), project_param
winniehell's avatar
winniehell committed
1372

1373
        expect(response).to have_http_status(200)
winniehell's avatar
winniehell committed
1374

1375
        project_param.each_pair do |k, v|
1376
          expect(json_response[k.to_s]).to eq(v)
1377 1378
        end
      end
winniehell's avatar
winniehell committed
1379 1380 1381

      it 'updates jobs_enabled' do
        project_param = { jobs_enabled: true }
winniehell's avatar
winniehell committed
1382

1383
        put api("/projects/#{project3.id}", user), project_param
winniehell's avatar
winniehell committed
1384

1385
        expect(response).to have_http_status(200)
winniehell's avatar
winniehell committed
1386

1387
        project_param.each_pair do |k, v|
1388
          expect(json_response[k.to_s]).to eq(v)
1389 1390 1391 1392 1393
        end
      end
    end

    context 'when authenticated as project master' do
1394
      it 'updates path' do
1395 1396
        project_param = { path: 'bar' }
        put api("/projects/#{project3.id}", user4), project_param
1397
        expect(response).to have_http_status(200)
1398
        project_param.each_pair do |k, v|
1399
          expect(json_response[k.to_s]).to eq(v)
1400 1401 1402
        end
      end

1403
      it 'updates other attributes' do
1404 1405 1406 1407 1408 1409 1410
        project_param = { issues_enabled: true,
                          wiki_enabled: true,
                          snippets_enabled: true,
                          merge_requests_enabled: true,
                          description: 'new description' }

        put api("/projects/#{project3.id}", user4), project_param
1411
        expect(response).to have_http_status(200)
1412
        project_param.each_pair do |k, v|
1413
          expect(json_response[k.to_s]).to eq(v)
1414 1415 1416
        end
      end

1417
      it 'does not update path to existing path' do
1418 1419
        project_param = { path: project.path }
        put api("/projects/#{project3.id}", user4), project_param
1420
        expect(response).to have_http_status(400)
1421
        expect(json_response['message']['path']).to eq(['has already been taken'])
1422 1423
      end

1424
      it 'does not update name' do
1425 1426
        project_param = { name: 'bar' }
        put api("/projects/#{project3.id}", user4), project_param
1427
        expect(response).to have_http_status(403)
1428 1429
      end

1430
      it 'does not update visibility_level' do
1431
        project_param = { visibility: 'public' }
1432
        put api("/projects/#{project3.id}", user4), project_param
1433
        expect(response).to have_http_status(403)
1434 1435 1436 1437
      end
    end

    context 'when authenticated as project developer' do
1438
      it 'does not update other attributes' do
1439 1440 1441 1442 1443
        project_param = { path: 'bar',
                          issues_enabled: true,
                          wiki_enabled: true,
                          snippets_enabled: true,
                          merge_requests_enabled: true,
1444 1445
                          description: 'new description',
                          request_access_enabled: true }
1446
        put api("/projects/#{project.id}", user3), project_param
1447
        expect(response).to have_http_status(403)
1448 1449 1450 1451
      end
    end
  end

1452
  describe 'POST /projects/:id/archive' do
1453 1454
    context 'on an unarchived project' do
      it 'archives the project' do
1455
        post api("/projects/#{project.id}/archive", user)
1456

1457
        expect(response).to have_http_status(201)
1458 1459 1460 1461 1462 1463 1464 1465 1466 1467
        expect(json_response['archived']).to be_truthy
      end
    end

    context 'on an archived project' do
      before do
        project.archive!
      end

      it 'remains archived' do
1468
        post api("/projects/#{project.id}/archive", user)
1469

1470
        expect(response).to have_http_status(201)
1471 1472
        expect(json_response['archived']).to be_truthy
      end
1473
    end
1474

1475 1476 1477 1478
    context 'user without archiving rights to the project' do
      before do
        project.team << [user3, :developer]
      end
1479

1480 1481 1482
      it 'rejects the action' do
        post api("/projects/#{project.id}/archive", user3)

1483
        expect(response).to have_http_status(403)
1484 1485 1486 1487
      end
    end
  end

1488
  describe 'POST /projects/:id/unarchive' do
1489 1490
    context 'on an unarchived project' do
      it 'remains unarchived' do
1491
        post api("/projects/#{project.id}/unarchive", user)
1492

1493
        expect(response).to have_http_status(201)
1494 1495 1496 1497 1498 1499 1500 1501 1502
        expect(json_response['archived']).to be_falsey
      end
    end

    context 'on an archived project' do
      before do
        project.archive!
      end

1503 1504
      it 'unarchives the project' do
        post api("/projects/#{project.id}/unarchive", user)
1505

1506
        expect(response).to have_http_status(201)
1507 1508
        expect(json_response['archived']).to be_falsey
      end
1509
    end
1510

1511 1512 1513 1514
    context 'user without archiving rights to the project' do
      before do
        project.team << [user3, :developer]
      end
1515

1516 1517 1518
      it 'rejects the action' do
        post api("/projects/#{project.id}/unarchive", user3)

1519
        expect(response).to have_http_status(403)
1520 1521 1522 1523
      end
    end
  end

1524 1525 1526
  describe 'POST /projects/:id/star' do
    context 'on an unstarred project' do
      it 'stars the project' do
1527
        expect { post api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(1)
1528

1529
        expect(response).to have_http_status(201)
1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540
        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
1541
        expect { post api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count }
1542

1543
        expect(response).to have_http_status(304)
1544 1545 1546 1547
      end
    end
  end

1548
  describe 'POST /projects/:id/unstar' do
1549 1550 1551 1552 1553 1554 1555
    context 'on a starred project' do
      before do
        user.toggle_star(project)
        project.reload
      end

      it 'unstars the project' do
1556
        expect { post api("/projects/#{project.id}/unstar", user) }.to change { project.reload.star_count }.by(-1)
1557

1558
        expect(response).to have_http_status(201)
1559 1560 1561 1562 1563 1564
        expect(json_response['star_count']).to eq(0)
      end
    end

    context 'on an unstarred project' do
      it 'does not modify the star count' do
1565
        expect { post api("/projects/#{project.id}/unstar", user) }.not_to change { project.reload.star_count }
1566

1567
        expect(response).to have_http_status(304)
1568 1569 1570 1571
      end
    end
  end

1572 1573
  describe 'DELETE /projects/:id' do
    context 'when authenticated as user' do
1574
      it 'removes project' do
1575
        delete api("/projects/#{project.id}", user)
1576 1577 1578

        expect(response).to have_http_status(202)
        expect(json_response['message']).to eql('202 Accepted')
1579 1580
      end

1581
      it_behaves_like '412 response' do
1582
        let(:success_status) { 202 }
1583 1584 1585
        let(:request) { api("/projects/#{project.id}", user) }
      end

1586
      it 'does not remove a project if not an owner' do
1587 1588 1589
        user3 = create(:user)
        project.team << [user3, :developer]
        delete api("/projects/#{project.id}", user3)
1590
        expect(response).to have_http_status(403)
1591 1592
      end

1593
      it 'does not remove a non existing project' do
1594
        delete api('/projects/1328', user)
1595
        expect(response).to have_http_status(404)
1596 1597
      end

1598
      it 'does not remove a project not attached to user' do
1599
        delete api("/projects/#{project.id}", user2)
1600
        expect(response).to have_http_status(404)
1601 1602 1603
      end
    end

1604
    context 'when authenticated as admin' do
1605
      it 'removes any existing project' do
1606
        delete api("/projects/#{project.id}", admin)
1607 1608 1609

        expect(response).to have_http_status(202)
        expect(json_response['message']).to eql('202 Accepted')
1610 1611
      end

1612
      it 'does not remove a non existing project' do
1613
        delete api('/projects/1328', admin)
1614
        expect(response).to have_http_status(404)
1615
      end
1616 1617

      it_behaves_like '412 response' do
1618
        let(:success_status) { 202 }
1619 1620
        let(:request) { api("/projects/#{project.id}", admin) }
      end
1621 1622
    end
  end
1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648

  describe 'POST /projects/:id/fork' do
    let(:project) do
      create(:project, :repository, creator: user, namespace: user.namespace)
    end
    let(:group) { create(:group) }
    let(:group2) do
      group = create(:group, name: 'group2_name')
      group.add_owner(user2)
      group
    end

    before do
      project.add_reporter(user2)
    end

    context 'when authenticated' do
      it 'forks if user has sufficient access to project' do
        post api("/projects/#{project.id}/fork", user2)

        expect(response).to have_http_status(201)
        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
1649
        expect(json_response['import_status']).to eq('scheduled')
1650
        expect(json_response).to include("import_error")
1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661
      end

      it 'forks if user is admin' do
        post api("/projects/#{project.id}/fork", admin)

        expect(response).to have_http_status(201)
        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
1662
        expect(json_response['import_status']).to eq('scheduled')
1663
        expect(json_response).to include("import_error")
1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 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
      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)

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

      it 'fails if forked project exists in the user namespace' do
        post api("/projects/#{project.id}/fork", user)

        expect(response).to have_http_status(409)
        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
        post api('/projects/424242/fork', user)

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

      it 'forks with explicit own user namespace id' do
        post api("/projects/#{project.id}/fork", user2), namespace: user2.namespace.id

        expect(response).to have_http_status(201)
        expect(json_response['owner']['id']).to eq(user2.id)
      end

      it 'forks with explicit own user name as namespace' do
        post api("/projects/#{project.id}/fork", user2), namespace: user2.username

        expect(response).to have_http_status(201)
        expect(json_response['owner']['id']).to eq(user2.id)
      end

      it 'forks to another user when admin' do
        post api("/projects/#{project.id}/fork", admin), namespace: user2.username

        expect(response).to have_http_status(201)
        expect(json_response['owner']['id']).to eq(user2.id)
      end

      it 'fails if trying to fork to another user when not admin' do
        post api("/projects/#{project.id}/fork", user2), namespace: admin.namespace.id

        expect(response).to have_http_status(404)
      end

      it 'fails if trying to fork to non-existent namespace' do
        post api("/projects/#{project.id}/fork", user2), namespace: 42424242

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

      it 'forks to owned group' do
        post api("/projects/#{project.id}/fork", user2), namespace: group2.name

        expect(response).to have_http_status(201)
        expect(json_response['namespace']['name']).to eq(group2.name)
      end

      it 'fails to fork to not owned group' do
        post api("/projects/#{project.id}/fork", user2), namespace: group.name

        expect(response).to have_http_status(404)
      end

      it 'forks to not owned group when admin' do
        post api("/projects/#{project.id}/fork", admin), namespace: group.name

        expect(response).to have_http_status(201)
        expect(json_response['namespace']['name']).to eq(group.name)
      end
    end

    context 'when unauthenticated' do
      it 'returns authentication error' do
        post api("/projects/#{project.id}/fork")

        expect(response).to have_http_status(401)
        expect(json_response['message']).to eq('401 Unauthorized')
      end
    end
  end
1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783

  describe 'POST /projects/:id/housekeeping' do
    let(:housekeeping) { Projects::HousekeepingService.new(project) }

    before do
      allow(Projects::HousekeepingService).to receive(:new).with(project).and_return(housekeeping)
    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)

        expect(response).to have_http_status(201)
      end

      context 'when housekeeping lease is taken' do
        it 'returns conflict' do
          expect(housekeeping).to receive(:execute).once.and_raise(Projects::HousekeepingService::LeaseTaken)

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

          expect(response).to have_http_status(409)
          expect(json_response['message']).to match(/Somebody already triggered housekeeping for this project/)
        end
      end
    end

    context 'when authenticated as developer' do
      before do
1784
        project_member
1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801
      end

      it 'returns forbidden error' do
        post api("/projects/#{project.id}/housekeeping", user3)

        expect(response).to have_http_status(403)
      end
    end

    context 'when unauthenticated' do
      it 'returns authentication error' do
        post api("/projects/#{project.id}/housekeeping")

        expect(response).to have_http_status(401)
      end
    end
  end
Nihad Abbasov's avatar
Nihad Abbasov committed
1802
end