projects_spec.rb 57.1 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(:empty_project, creator_id: user.id, namespace: user.namespace) }
  let(:project2) { create(:empty_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(:empty_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 64 65 66 67 68 69 70 71 72
    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
          create(:empty_project, :public)
        end

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

73 74 75 76 77 78 79
    let!(:public_project) { create(:empty_project, :public, name: 'public_project') }
    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 106 107 108 109 110 111 112 113 114
      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
          create(:empty_project, :public, group: create(:group))
        end

        it_behaves_like 'projects response without N + 1 queries' do
          let(:current_user) { user }
          let(:additional_project) { create(:empty_project, :public, group: create(:group)) }
        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
      context 'and with simple=true' do
tiagonbotelho's avatar
tiagonbotelho committed
163
        it 'returns a simplified version of all the projects' do
Douwe Maan's avatar
Douwe Maan committed
164
          expected_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace)
165

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

168
          expect(response).to have_http_status(200)
169
          expect(response).to include_pagination_headers
170
          expect(json_response).to be_an Array
171
          expect(json_response.first.keys).to match_array expected_keys
172 173 174
        end
      end

175
      context 'and using search' do
176 177 178 179 180 181
        it_behaves_like 'projects response' do
          let(:filter) { { search: project.name } }
          let(:current_user) { user }
          let(:projects) { [project] }
        end
      end
182

183
      context 'and membership=true' do
184
        it_behaves_like 'projects response' do
185
          let(:filter) { { membership: true } }
186 187
          let(:current_user) { user }
          let(:projects) { [project, project2, project3] }
188 189 190
        end
      end

Josh Frye's avatar
Josh Frye committed
191
      context 'and using the visibility filter' do
192
        it 'filters based on private visibility param' do
Josh Frye's avatar
Josh Frye committed
193
          get api('/projects', user), { visibility: 'private' }
194

195
          expect(response).to have_http_status(200)
196
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
197
          expect(json_response).to be_an Array
198
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id, project2.id, project3.id)
Josh Frye's avatar
Josh Frye committed
199 200
        end

201
        it 'filters based on internal visibility param' do
202 203
          project2.update_attribute(:visibility_level, Gitlab::VisibilityLevel::INTERNAL)

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

206
          expect(response).to have_http_status(200)
207
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
208
          expect(json_response).to be_an Array
209
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project2.id)
Josh Frye's avatar
Josh Frye committed
210 211
        end

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

215
          expect(response).to have_http_status(200)
216
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
217
          expect(json_response).to be_an Array
218
          expect(json_response.map { |p| p['id'] }).to contain_exactly(public_project.id)
Josh Frye's avatar
Josh Frye committed
219 220 221
        end
      end

222
      context 'and using sorting' do
223
        it 'returns the correct order when sorted by id' do
224
          get api('/projects', user), { order_by: 'id', sort: 'desc' }
225

226
          expect(response).to have_http_status(200)
227
          expect(response).to include_pagination_headers
228 229
          expect(json_response).to be_an Array
          expect(json_response.first['id']).to eq(project3.id)
230 231
        end
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
232

233 234 235
      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
236

237
          expect(response).to have_http_status(200)
238
          expect(response).to include_pagination_headers
239 240 241
          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)
242
        end
243 244
      end

245 246
      context 'and with starred=true' do
        let(:public_project) { create(:empty_project, :public) }
247

248
        before do
249
          project_member
250 251
          user3.update_attributes(starred_projects: [project, project2, project3, public_project])
        end
Marin Jankovski's avatar
Marin Jankovski committed
252

253 254
        it 'returns the starred projects viewable by the user' do
          get api('/projects', user3), starred: true
255

256
          expect(response).to have_http_status(200)
257
          expect(response).to include_pagination_headers
258 259
          expect(json_response).to be_an Array
          expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
260
        end
261 262
      end

263
      context 'and with all query parameters' do
264
        let!(:project5) { create(:empty_project, :public, path: 'gitlab5', namespace: create(:namespace)) }
265 266 267 268
        let!(:project6) { create(:empty_project, :public, path: 'project6', namespace: user.namespace) }
        let!(:project7) { create(:empty_project, :public, path: 'gitlab7', namespace: user.namespace) }
        let!(:project8) { create(:empty_project, path: 'gitlab8', namespace: user.namespace) }
        let!(:project9) { create(:empty_project, :public, path: 'gitlab9') }
269

270
        before do
271
          user.update_attributes(starred_projects: [project5, project7, project8, project9])
272
        end
273

274
        context 'including owned filter' do
275
          it 'returns only projects that satisfy all query parameters' do
276
            get api('/projects', user), { visibility: 'public', owned: true, starred: true, search: 'gitlab' }
277

278 279 280 281 282 283 284
            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
285

286
        context 'including membership filter' do
287 288 289 290 291 292
          before do
            create(:project_member,
                   user: user,
                   project: project5,
                   access_level: ProjectMember::MASTER)
          end
293

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

297 298 299 300
            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)
301
            expect(json_response.map { |project| project['id'] }).to contain_exactly(project5.id, project7.id)
302
          end
303
        end
304 305
      end
    end
306

307
    context 'when authenticated as a different user' do
308
      it_behaves_like 'projects response' do
309
        let(:filter) { {} }
310 311 312
        let(:current_user) { user2 }
        let(:projects) { [public_project] }
      end
313 314
    end

315 316
    context 'when authenticated as admin' do
      it_behaves_like 'projects response' do
317
        let(:filter) { {} }
318 319 320
        let(:current_user) { admin }
        let(:projects) { Project.all }
      end
321 322 323
    end
  end

324 325
  describe 'POST /projects' do
    context 'maximum number of projects reached' do
326
      it 'does not create new project and respond with 403' do
327
        allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0)
328 329
        expect { post api('/projects', user2), name: 'foo' }
          .to change {Project.count}.by(0)
330
        expect(response).to have_http_status(403)
331 332 333
      end
    end

334
    it 'creates new project without path but with name and returns 201' do
335 336
      expect { post api('/projects', user), name: 'Foo Project' }
        .to change { Project.count }.by(1)
337
      expect(response).to have_http_status(201)
338 339 340 341 342 343 344 345

      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
346 347
      expect { post api('/projects', user), path: 'foo_project' }
        .to change { Project.count }.by(1)
348
      expect(response).to have_http_status(201)
349 350 351 352 353 354 355

      project = Project.first

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

356
    it 'creates new project with name and path and returns 201' do
357 358
      expect { post api('/projects', user), path: 'path-project-Foo', name: 'Foo Project' }
        .to change { Project.count }.by(1)
359 360 361 362 363
      expect(response).to have_http_status(201)

      project = Project.first

      expect(project.name).to eq('Foo Project')
364
      expect(project.path).to eq('path-project-Foo')
365 366
    end

367
    it 'creates last project before reaching project limit' do
368
      allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1)
369
      post api('/projects', user2), name: 'foo'
370
      expect(response).to have_http_status(201)
371 372
    end

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

378
    it "assigns attributes to project" do
379
      project = attributes_for(:project, {
380
        path: 'camelCasePath',
381
        issues_enabled: false,
winniehell's avatar
winniehell committed
382
        jobs_enabled: false,
383
        merge_requests_enabled: false,
384
        wiki_enabled: false,
385
        only_allow_merge_if_pipeline_succeeds: false,
386
        request_access_enabled: true,
387
        only_allow_merge_if_all_discussions_are_resolved: false,
388
        ci_config_path: 'a/custom/path'
Alex Denisov's avatar
Alex Denisov committed
389 390
      })

391
      post api('/projects', user), project
Alex Denisov's avatar
Alex Denisov committed
392

winniehell's avatar
winniehell committed
393 394
      expect(response).to have_http_status(201)

395
      project.each_pair do |k, v|
396
        next if %i[has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled].include?(k)
397
        expect(json_response[k.to_s]).to eq(v)
Alex Denisov's avatar
Alex Denisov committed
398
      end
399 400 401 402 403 404

      # 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)
405
    end
406

407
    it 'sets a project as public' do
408
      project = attributes_for(:project, visibility: 'public')
409

410
      post api('/projects', user), project
411 412

      expect(json_response['visibility']).to eq('public')
413 414
    end

415
    it 'sets a project as internal' do
416 417
      project = attributes_for(:project, visibility: 'internal')

418
      post api('/projects', user), project
419 420

      expect(json_response['visibility']).to eq('internal')
421 422
    end

423
    it 'sets a project as private' do
424 425
      project = attributes_for(:project, visibility: 'private')

426
      post api('/projects', user), project
427 428

      expect(json_response['visibility']).to eq('private')
429 430
    end

431 432 433
    it 'sets tag list to a project' do
      project = attributes_for(:project, tag_list: %w[tagFirst tagSecond])

434
      post api('/projects', user), project
435 436

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

439 440 441
    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'))

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

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

448
    it 'sets a project as allowing merge even if build fails' do
449
      project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
450
      post api('/projects', user), project
451
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
452 453
    end

454 455
    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 })
456
      post api('/projects', user), project
457
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
458 459
    end

460 461 462
    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 })

463
      post api('/projects', user), project
464 465

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

468 469 470
    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)

471
      post api('/projects', user), project
472 473 474 475

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

476 477 478 479 480 481 482 483
    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

484 485 486 487 488 489
    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)
490 491
    end

492
    context 'when a visibility level is restricted' do
493
      let(:project_param) { attributes_for(:project, visibility: 'public') }
494

495
      before do
496
        stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
497 498
      end

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

502
        expect(response).to have_http_status(400)
503 504 505 506 507
        expect(json_response['message']['visibility_level'].first).to(
          match('restricted by your GitLab administrator')
        )
      end

508
      it 'allows an admin to override restricted visibility settings' do
509 510
        post api('/projects', admin), project_param

511
        expect(json_response['visibility']).to eq('public')
512 513
      end
    end
514 515
  end

vanadium23's avatar
vanadium23 committed
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
  describe 'GET /users/:user_id/projects/' do
    let!(:public_project) { create(:empty_project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }

    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

536
  describe 'POST /projects/user/:id' do
537 538 539
    before do
      expect(project).to be_persisted
    end
Angus MacArthur's avatar
Angus MacArthur committed
540

541 542
    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)
543
      expect(response).to have_http_status(201)
544 545 546 547 548 549 550 551

      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
552 553
      expect { post api("/projects/user/#{user.id}", admin), path: 'path-project-Foo', name: 'Foo Project' }
        .to change { Project.count }.by(1)
554 555 556 557 558 559
      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
560 561
    end

562
    it 'responds with 400 on failure and not project' do
563 564
      expect { post api("/projects/user/#{user.id}", admin) }
        .not_to change { Project.count }
565

566
      expect(response).to have_http_status(400)
567
      expect(json_response['error']).to eq('name is missing')
Angus MacArthur's avatar
Angus MacArthur committed
568 569
    end

570
    it 'assigns attributes to project' do
Angus MacArthur's avatar
Angus MacArthur committed
571
      project = attributes_for(:project, {
572 573
        issues_enabled: false,
        merge_requests_enabled: false,
574 575
        wiki_enabled: false,
        request_access_enabled: true
Angus MacArthur's avatar
Angus MacArthur committed
576 577 578 579
      })

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

580
      expect(response).to have_http_status(201)
581
      project.each_pair do |k, v|
582
        next if %i[has_external_issue_tracker path].include?(k)
583
        expect(json_response[k.to_s]).to eq(v)
Angus MacArthur's avatar
Angus MacArthur committed
584 585
      end
    end
586

587
    it 'sets a project as public' do
588
      project = attributes_for(:project, visibility: 'public')
589

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

      expect(response).to have_http_status(201)
593
      expect(json_response['visibility']).to eq('public')
594
    end
595

596
    it 'sets a project as internal' do
597 598
      project = attributes_for(:project, visibility: 'internal')

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

      expect(response).to have_http_status(201)
602
      expect(json_response['visibility']).to eq('internal')
603 604
    end

605
    it 'sets a project as private' do
606 607
      project = attributes_for(:project, visibility: 'private')

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

      expect(json_response['visibility']).to eq('private')
611
    end
612

613
    it 'sets a project as allowing merge even if build fails' do
614
      project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
615
      post api("/projects/user/#{user.id}", admin), project
616
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
617 618
    end

619 620
    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 })
621
      post api("/projects/user/#{user.id}", admin), project
622
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
623
    end
624

625 626 627
    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 })

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

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

633 634 635
    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 })

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

      expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
639
    end
Angus MacArthur's avatar
Angus MacArthur committed
640 641
  end

642
  describe "POST /projects/:id/uploads" do
643 644 645
    before do
      project
    end
646 647 648 649

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

650
      expect(response).to have_http_status(201)
651 652 653 654 655 656
      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

657
  describe 'GET /projects/:id' do
658 659
    context 'when unauthenticated' do
      it 'returns the public projects' do
660
        public_project = create(:empty_project, :public)
661

662
        get api("/projects/#{public_project.id}")
663

664 665 666 667 668
        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)
        expect(json_response.keys).not_to include('permissions')
      end
669
    end
670

671 672 673 674 675
    context 'when authenticated' do
      before do
        project
        project_member
      end
676

677 678 679
      it 'returns a project by id' do
        group = create(:group)
        link = create(:project_group_link, project: project, group: group)
680

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

683 684 685 686 687 688
        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
689
        expect(json_response['visibility']).to be_present
690 691 692 693 694 695 696 697 698 699
        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
700
        expect(json_response['jobs_enabled']).to be_present
701 702 703 704 705 706 707
        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
708 709
        expect(json_response['import_status']).to be_present
        expect(json_response).to include("import_error")
710 711 712
        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
713
        expect(json_response['public_jobs']).to be_present
714
        expect(json_response['ci_config_path']).to be_nil
715 716 717 718 719
        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)
720
        expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
721 722
        expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
      end
723

724 725 726 727 728
      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
729

730 731 732 733 734
      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
735

736 737 738 739
      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)
740
      end
741

742 743
      it 'handles users with dots' do
        dot_user = create(:user, username: 'dot.user')
744
        project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace)
745

746 747 748
        get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user)
        expect(response).to have_http_status(200)
        expect(json_response['name']).to eq(project.name)
749 750
      end

751 752
      it 'exposes namespace fields' do
        get api("/projects/#{project.id}", user)
753

754 755 756 757 758 759
        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,
760 761
          'full_path' => user.namespace.full_path,
          'parent_id' => nil
762
        })
763 764
      end

765 766
      it "does not include statistics by default" do
        get api("/projects/#{project.id}", user)
767

768 769 770
        expect(response).to have_http_status(200)
        expect(json_response).not_to include 'statistics'
      end
771

772 773
      it "includes statistics if requested" do
        get api("/projects/#{project.id}", user), statistics: true
774

775 776
        expect(response).to have_http_status(200)
        expect(json_response).to include 'statistics'
777
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
778

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

782 783
        expect(response).to have_http_status(200)
        expect(json_response).to include("import_error")
784 785
      end

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

789 790 791
        expect(response).to have_http_status(200)
        expect(json_response).not_to include("import_error")
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
792

793 794
      describe 'permissions' do
        context 'all projects' do
795 796 797
          before do
            project.team << [user, :master]
          end
798 799 800 801 802

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

            expect(response).to have_http_status(200)
803 804
            expect(json_response.first['permissions']['project_access']['access_level'])
            .to eq(Gitlab::Access::MASTER)
805 806 807 808 809 810 811 812 813 814
            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)
815 816
            expect(json_response['permissions']['project_access']['access_level'])
            .to eq(Gitlab::Access::MASTER)
817 818
            expect(json_response['permissions']['group_access']).to be_nil
          end
819
        end
820

821
        context 'group project' do
822
          let(:project2) { create(:empty_project, group: create(:group)) }
823

824 825 826
          before do
            project2.group.add_owner(user)
          end
827

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

831 832
            expect(response).to have_http_status(200)
            expect(json_response['permissions']['project_access']).to be_nil
833 834
            expect(json_response['permissions']['group_access']['access_level'])
            .to eq(Gitlab::Access::OWNER)
835
          end
836
        end
837
      end
838
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
839
  end
840

841 842 843 844
  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)
845

846 847
        user = project.namespace.owner

848
        expect(response).to have_http_status(200)
849
        expect(response).to include_pagination_headers
850 851 852 853
        expect(json_response).to be_an Array
        expect(json_response.size).to eq(1)

        first_user = json_response.first
854 855
        expect(first_user['username']).to eq(user.username)
        expect(first_user['name']).to eq(user.name)
856
        expect(first_user.keys).to contain_exactly(*%w[name username id state avatar_url web_url])
857
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
858 859
    end

860 861
    context 'when unauthenticated' do
      it_behaves_like 'project users response' do
862
        let(:project) { create(:empty_project, :public) }
863 864
        let(:current_user) { nil }
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
865 866
    end

867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887
    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
888 889 890
    end
  end

891
  describe 'GET /projects/:id/snippets' do
892 893 894
    before do
      snippet
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
895

896
    it 'returns an array of project snippets' do
897
      get api("/projects/#{project.id}/snippets", user)
898

899
      expect(response).to have_http_status(200)
900
      expect(response).to include_pagination_headers
901 902
      expect(json_response).to be_an Array
      expect(json_response.first['title']).to eq(snippet.title)
903 904 905
    end
  end

906
  describe 'GET /projects/:id/snippets/:snippet_id' do
907
    it 'returns a project snippet' do
908
      get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
909
      expect(response).to have_http_status(200)
910
      expect(json_response['title']).to eq(snippet.title)
Nihad Abbasov's avatar
Nihad Abbasov committed
911
    end
912

913
    it 'returns a 404 error if snippet id not found' do
914
      get api("/projects/#{project.id}/snippets/1234", user)
915
      expect(response).to have_http_status(404)
916
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
917 918
  end

919
  describe 'POST /projects/:id/snippets' do
920
    it 'creates a new project snippet' do
921
      post api("/projects/#{project.id}/snippets", user),
922
        title: 'api test', file_name: 'sample.rb', code: 'test', visibility: 'private'
923
      expect(response).to have_http_status(201)
924
      expect(json_response['title']).to eq('api test')
Nihad Abbasov's avatar
Nihad Abbasov committed
925
    end
926

927
    it 'returns a 400 error if invalid snippet is given' do
928 929
      post api("/projects/#{project.id}/snippets", user)
      expect(status).to eq(400)
930
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
931 932
  end

933
  describe 'PUT /projects/:id/snippets/:snippet_id' do
934
    it 'updates an existing project snippet' do
935
      put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
936
        code: 'updated code'
937
      expect(response).to have_http_status(200)
938 939
      expect(json_response['title']).to eq('example')
      expect(snippet.reload.content).to eq('updated code')
940
    end
941

942
    it 'updates an existing project snippet with new title' do
943
      put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
944
        title: 'other api test'
945
      expect(response).to have_http_status(200)
946
      expect(json_response['title']).to eq('other api test')
947
    end
948 949
  end

950
  describe 'DELETE /projects/:id/snippets/:snippet_id' do
951 952 953
    before do
      snippet
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
954

955
    it 'deletes existing project snippet' do
956
      expect do
957
        delete api("/projects/#{project.id}/snippets/#{snippet.id}", user)
958 959

        expect(response).to have_http_status(204)
960
      end.to change { Snippet.count }.by(-1)
961 962
    end

963
    it 'returns 404 when deleting unknown snippet id' do
964
      delete api("/projects/#{project.id}/snippets/1234", user)
965
      expect(response).to have_http_status(404)
Nihad Abbasov's avatar
Nihad Abbasov committed
966 967
    end
  end
968

969
  describe 'GET /projects/:id/snippets/:snippet_id/raw' do
970
    it 'gets a raw project snippet' do
971
      get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user)
972
      expect(response).to have_http_status(200)
973
    end
974

975
    it 'returns a 404 error if raw project snippet not found' do
976
      get api("/projects/#{project.id}/snippets/5555/raw", user)
977
      expect(response).to have_http_status(404)
978
    end
979
  end
980

981
  describe 'fork management' do
982 983
    let(:project_fork_target) { create(:empty_project) }
    let(:project_fork_source) { create(:empty_project, :public) }
984

985
    describe 'POST /projects/:id/fork/:forked_from_id' do
986
      let(:new_project_fork_source) { create(:empty_project, :public) }
987

988
      it "is not available for non admin users" do
989
        post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
990
        expect(response).to have_http_status(403)
991 992
      end

993
      it 'allows project to be forked from an existing project' do
994
        expect(project_fork_target.forked?).not_to be_truthy
995
        post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
996
        expect(response).to have_http_status(201)
997
        project_fork_target.reload
998 999 1000
        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
1001 1002
      end

1003
      it 'fails if forked_from project which does not exist' do
1004
        post api("/projects/#{project_fork_target.id}/fork/9999", admin)
1005
        expect(response).to have_http_status(404)
1006 1007
      end

1008
      it 'fails with 409 if already forked' do
1009 1010
        post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
        project_fork_target.reload
1011
        expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
1012
        post api("/projects/#{project_fork_target.id}/fork/#{new_project_fork_source.id}", admin)
1013
        expect(response).to have_http_status(409)
1014
        project_fork_target.reload
1015 1016
        expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
        expect(project_fork_target.forked?).to be_truthy
1017 1018 1019
      end
    end

1020
    describe 'DELETE /projects/:id/fork' do
1021
      it "is not visible to users outside group" do
1022
        delete api("/projects/#{project_fork_target.id}/fork", user)
1023
        expect(response).to have_http_status(404)
1024 1025
      end

1026
      context 'when users belong to project group' do
1027
        let(:project_fork_target) { create(:empty_project, group: create(:group)) }
1028

1029 1030 1031 1032 1033
        before do
          project_fork_target.group.add_owner user
          project_fork_target.group.add_developer user2
        end

1034
        it 'is forbidden to non-owner users' do
1035
          delete api("/projects/#{project_fork_target.id}/fork", user2)
1036
          expect(response).to have_http_status(403)
1037 1038
        end

1039
        it 'makes forked project unforked' do
1040 1041 1042 1043
          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
1044

1045
          delete api("/projects/#{project_fork_target.id}/fork", admin)
1046 1047

          expect(response).to have_http_status(204)
1048 1049 1050 1051 1052
          project_fork_target.reload
          expect(project_fork_target.forked_from_project).to be_nil
          expect(project_fork_target.forked?).not_to be_truthy
        end

1053
        it 'is idempotent if not forked' do
1054 1055
          expect(project_fork_target.forked_from_project).to be_nil
          delete api("/projects/#{project_fork_target.id}/fork", admin)
1056
          expect(response).to have_http_status(304)
1057 1058
          expect(project_fork_target.reload.forked_from_project).to be_nil
        end
1059 1060 1061
      end
    end
  end
1062

1063 1064 1065
  describe "POST /projects/:id/share" do
    let(:group) { create(:group) }

1066
    it "shares project with group" do
1067 1068
      expires_at = 10.days.from_now.to_date

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

1073
      expect(response).to have_http_status(201)
1074 1075 1076
      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)
1077 1078
    end

1079
    it "returns a 400 error when group id is not given" do
1080
      post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER
1081
      expect(response).to have_http_status(400)
1082 1083
    end

1084
    it "returns a 400 error when access level is not given" do
1085
      post api("/projects/#{project.id}/share", user), group_id: group.id
1086
      expect(response).to have_http_status(400)
1087 1088
    end

1089
    it "returns a 400 error when sharing is disabled" do
1090 1091
      project.namespace.update(share_with_group_lock: true)
      post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER
1092
      expect(response).to have_http_status(400)
1093 1094
    end

1095 1096 1097 1098 1099
    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

1100
      expect(response).to have_http_status(404)
1101 1102
    end

1103 1104 1105
    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

1106
      expect(response).to have_http_status(404)
1107 1108
    end

1109
    it "returns a 400 error when wrong params passed" do
1110
      post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234
1111 1112 1113

      expect(response).to have_http_status(400)
      expect(json_response['error']).to eq 'group_access does not have a valid value'
1114 1115 1116
    end
  end

1117 1118 1119 1120
  describe 'DELETE /projects/:id/share/:group_id' do
    it 'returns 204 when deleting a group share' do
      group = create(:group, :public)
      create(:project_group_link, group: group, project: project)
1121

1122 1123 1124 1125
      delete api("/projects/#{project.id}/share/#{group.id}", user)

      expect(response).to have_http_status(204)
      expect(project.project_group_links).to be_empty
1126 1127
    end

1128 1129 1130 1131
    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)
1132 1133
    end

1134 1135 1136 1137 1138 1139 1140 1141 1142 1143
    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)
1144 1145
    end
  end
1146

1147
  describe 'PUT /projects/:id' do
1148 1149 1150 1151 1152 1153 1154 1155 1156 1157
    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
1158

1159 1160
    it 'returns 400 when nothing sent' do
      project_param = {}
winniehell's avatar
winniehell committed
1161

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

1164 1165 1166
      expect(response).to have_http_status(400)
      expect(json_response['error']).to match('at least one parameter must be provided')
    end
1167 1168

    context 'when unauthenticated' do
1169
      it 'returns authentication error' do
1170
        project_param = { name: 'bar' }
winniehell's avatar
winniehell committed
1171

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

1174
        expect(response).to have_http_status(401)
1175 1176 1177 1178
      end
    end

    context 'when authenticated as project owner' do
1179
      it 'updates name' do
1180
        project_param = { name: 'bar' }
winniehell's avatar
winniehell committed
1181

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

1184
        expect(response).to have_http_status(200)
winniehell's avatar
winniehell committed
1185

1186
        project_param.each_pair do |k, v|
1187
          expect(json_response[k.to_s]).to eq(v)
1188 1189 1190
        end
      end

1191
      it 'updates visibility_level' do
1192
        project_param = { visibility: 'public' }
winniehell's avatar
winniehell committed
1193

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

1196
        expect(response).to have_http_status(200)
winniehell's avatar
winniehell committed
1197

1198
        project_param.each_pair do |k, v|
1199
          expect(json_response[k.to_s]).to eq(v)
1200 1201 1202
        end
      end

1203
      it 'updates visibility_level from public to private' do
1204
        project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC })
1205
        project_param = { visibility: 'private' }
1206 1207

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

1209
        expect(response).to have_http_status(200)
winniehell's avatar
winniehell committed
1210

1211 1212 1213
        project_param.each_pair do |k, v|
          expect(json_response[k.to_s]).to eq(v)
        end
winniehell's avatar
winniehell committed
1214

1215
        expect(json_response['visibility']).to eq('private')
1216 1217
      end

1218
      it 'does not update name to existing name' do
1219
        project_param = { name: project3.name }
winniehell's avatar
winniehell committed
1220

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

1223
        expect(response).to have_http_status(400)
1224
        expect(json_response['message']['name']).to eq(['has already been taken'])
1225 1226
      end

1227 1228 1229 1230 1231 1232 1233 1234 1235
      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

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

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

1241
        expect(response).to have_http_status(200)
winniehell's avatar
winniehell committed
1242

1243
        project_param.each_pair do |k, v|
1244
          expect(json_response[k.to_s]).to eq(v)
1245 1246
        end
      end
winniehell's avatar
winniehell committed
1247 1248 1249

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

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

1253
        expect(response).to have_http_status(200)
winniehell's avatar
winniehell committed
1254

1255
        project_param.each_pair do |k, v|
1256
          expect(json_response[k.to_s]).to eq(v)
1257 1258 1259 1260 1261
        end
      end
    end

    context 'when authenticated as project master' do
1262
      it 'updates path' do
1263 1264
        project_param = { path: 'bar' }
        put api("/projects/#{project3.id}", user4), project_param
1265
        expect(response).to have_http_status(200)
1266
        project_param.each_pair do |k, v|
1267
          expect(json_response[k.to_s]).to eq(v)
1268 1269 1270
        end
      end

1271
      it 'updates other attributes' do
1272 1273 1274 1275 1276 1277 1278
        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
1279
        expect(response).to have_http_status(200)
1280
        project_param.each_pair do |k, v|
1281
          expect(json_response[k.to_s]).to eq(v)
1282 1283 1284
        end
      end

1285
      it 'does not update path to existing path' do
1286 1287
        project_param = { path: project.path }
        put api("/projects/#{project3.id}", user4), project_param
1288
        expect(response).to have_http_status(400)
1289
        expect(json_response['message']['path']).to eq(['has already been taken'])
1290 1291
      end

1292
      it 'does not update name' do
1293 1294
        project_param = { name: 'bar' }
        put api("/projects/#{project3.id}", user4), project_param
1295
        expect(response).to have_http_status(403)
1296 1297
      end

1298
      it 'does not update visibility_level' do
1299
        project_param = { visibility: 'public' }
1300
        put api("/projects/#{project3.id}", user4), project_param
1301
        expect(response).to have_http_status(403)
1302 1303 1304 1305
      end
    end

    context 'when authenticated as project developer' do
1306
      it 'does not update other attributes' do
1307 1308 1309 1310 1311
        project_param = { path: 'bar',
                          issues_enabled: true,
                          wiki_enabled: true,
                          snippets_enabled: true,
                          merge_requests_enabled: true,
1312 1313
                          description: 'new description',
                          request_access_enabled: true }
1314
        put api("/projects/#{project.id}", user3), project_param
1315
        expect(response).to have_http_status(403)
1316 1317 1318 1319
      end
    end
  end

1320
  describe 'POST /projects/:id/archive' do
1321 1322
    context 'on an unarchived project' do
      it 'archives the project' do
1323
        post api("/projects/#{project.id}/archive", user)
1324

1325
        expect(response).to have_http_status(201)
1326 1327 1328 1329 1330 1331 1332 1333 1334 1335
        expect(json_response['archived']).to be_truthy
      end
    end

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

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

1338
        expect(response).to have_http_status(201)
1339 1340
        expect(json_response['archived']).to be_truthy
      end
1341
    end
1342

1343 1344 1345 1346
    context 'user without archiving rights to the project' do
      before do
        project.team << [user3, :developer]
      end
1347

1348 1349 1350
      it 'rejects the action' do
        post api("/projects/#{project.id}/archive", user3)

1351
        expect(response).to have_http_status(403)
1352 1353 1354 1355
      end
    end
  end

1356
  describe 'POST /projects/:id/unarchive' do
1357 1358
    context 'on an unarchived project' do
      it 'remains unarchived' do
1359
        post api("/projects/#{project.id}/unarchive", user)
1360

1361
        expect(response).to have_http_status(201)
1362 1363 1364 1365 1366 1367 1368 1369 1370
        expect(json_response['archived']).to be_falsey
      end
    end

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

1371 1372
      it 'unarchives the project' do
        post api("/projects/#{project.id}/unarchive", user)
1373

1374
        expect(response).to have_http_status(201)
1375 1376
        expect(json_response['archived']).to be_falsey
      end
1377
    end
1378

1379 1380 1381 1382
    context 'user without archiving rights to the project' do
      before do
        project.team << [user3, :developer]
      end
1383

1384 1385 1386
      it 'rejects the action' do
        post api("/projects/#{project.id}/unarchive", user3)

1387
        expect(response).to have_http_status(403)
1388 1389 1390 1391
      end
    end
  end

1392 1393 1394
  describe 'POST /projects/:id/star' do
    context 'on an unstarred project' do
      it 'stars the project' do
1395
        expect { post api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(1)
1396

1397
        expect(response).to have_http_status(201)
1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408
        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
1409
        expect { post api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count }
1410

1411
        expect(response).to have_http_status(304)
1412 1413 1414 1415
      end
    end
  end

1416
  describe 'POST /projects/:id/unstar' do
1417 1418 1419 1420 1421 1422 1423
    context 'on a starred project' do
      before do
        user.toggle_star(project)
        project.reload
      end

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

1426
        expect(response).to have_http_status(201)
1427 1428 1429 1430 1431 1432
        expect(json_response['star_count']).to eq(0)
      end
    end

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

1435
        expect(response).to have_http_status(304)
1436 1437 1438 1439
      end
    end
  end

1440 1441
  describe 'DELETE /projects/:id' do
    context 'when authenticated as user' do
1442
      it 'removes project' do
1443
        delete api("/projects/#{project.id}", user)
1444 1445 1446

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

1449
      it 'does not remove a project if not an owner' do
1450 1451 1452
        user3 = create(:user)
        project.team << [user3, :developer]
        delete api("/projects/#{project.id}", user3)
1453
        expect(response).to have_http_status(403)
1454 1455
      end

1456
      it 'does not remove a non existing project' do
1457
        delete api('/projects/1328', user)
1458
        expect(response).to have_http_status(404)
1459 1460
      end

1461
      it 'does not remove a project not attached to user' do
1462
        delete api("/projects/#{project.id}", user2)
1463
        expect(response).to have_http_status(404)
1464 1465 1466
      end
    end

1467
    context 'when authenticated as admin' do
1468
      it 'removes any existing project' do
1469
        delete api("/projects/#{project.id}", admin)
1470 1471 1472

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

1475
      it 'does not remove a non existing project' do
1476
        delete api('/projects/1328', admin)
1477
        expect(response).to have_http_status(404)
1478 1479 1480
      end
    end
  end
1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506

  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
1507
        expect(json_response['import_status']).to eq('scheduled')
1508
        expect(json_response).to include("import_error")
1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519
      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
1520
        expect(json_response['import_status']).to eq('scheduled')
1521
        expect(json_response).to include("import_error")
1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610
      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
1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641

  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
1642
        project_member
1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659
      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
1660
end