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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

124
      it 'includes open_issues_count' do
Stan Hu's avatar
Stan Hu committed
125
        get api('/projects', user)
126

Stan Hu's avatar
Stan Hu committed
127
        expect(response.status).to eq 200
128
        expect(response).to include_pagination_headers
Stan Hu's avatar
Stan Hu committed
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
Felipe Artur's avatar
Felipe Artur committed
134
        project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
Stan Hu's avatar
Stan Hu committed
135
136

        get api('/projects', user)
137

Stan Hu's avatar
Stan Hu committed
138
        expect(response.status).to eq 200
139
        expect(response).to include_pagination_headers
Stan Hu's avatar
Stan Hu committed
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')
Stan Hu's avatar
Stan Hu committed
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')
Stan Hu's avatar
Stan Hu committed
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'
Stan Hu's avatar
Stan Hu committed
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
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
275
276
      end

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

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

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

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

295
      context 'and with all query parameters' do
296
297
298
299
300
        let!(:project5) { create(:project, :public, path: 'gitlab5', namespace: create(:namespace)) }
        let!(:project6) { create(:project, :public, path: 'project6', namespace: user.namespace) }
        let!(:project7) { create(:project, :public, path: 'gitlab7', namespace: user.namespace) }
        let!(:project8) { create(:project, path: 'gitlab8', namespace: user.namespace) }
        let!(:project9) { create(:project, :public, path: 'gitlab9') }
301

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

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

310
311
312
313
314
315
316
            expect(response).to have_http_status(200)
            expect(response).to include_pagination_headers
            expect(json_response).to be_an Array
            expect(json_response.size).to eq(1)
            expect(json_response.first['id']).to eq(project7.id)
          end
        end
317

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

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

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

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

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

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

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

      project = Project.first

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

    it 'creates new project without name but with path and returns 201' do
378
379
      expect { post api('/projects', user), path: 'foo_project' }
        .to change { Project.count }.by(1)
380
      expect(response).to have_http_status(201)
381
382
383
384
385
386
387

      project = Project.first

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

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

      project = Project.first

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

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

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

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

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

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

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

      # Check feature permissions attributes
      project = Project.find_by_path(project[:path])
      expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED)
      expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::DISABLED)
      expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED)
437
    end
438

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

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

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

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

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

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

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

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

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

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

466
      post api('/projects', user), project
vanadium23's avatar
vanadium23 committed
467
468

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

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

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

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

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

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

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

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

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

500
501
502
    it 'sets a project as allowing merge if only_allow_merge_if_all_discussions_are_resolved is nil' do
      project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: nil)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      expect(response).to have_http_status(200)
      expect(response).to include_pagination_headers
      expect(json_response).to be_an Array
      expect(json_response.map { |project| project['id'] }).to contain_exactly(public_project.id)
    end
  end

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

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

      project = Project.first

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

    it 'creates new project with name and path and returns 201' do
584
585
      expect { post api("/projects/user/#{user.id}", admin), path: 'path-project-Foo', name: 'Foo Project' }
        .to change { Project.count }.by(1)
586
587
588
589
590
591
      expect(response).to have_http_status(201)

      project = Project.first

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

716
717
718
719
720
721
        expect(response).to have_http_status(200)
        expect(json_response['id']).to eq(project.id)
        expect(json_response['description']).to eq(project.description)
        expect(json_response['default_branch']).to eq(project.default_branch)
        expect(json_response['tag_list']).to be_an Array
        expect(json_response['archived']).to be_falsey
722
        expect(json_response['visibility']).to be_present
723
724
725
726
727
728
729
730
731
732
        expect(json_response['ssh_url_to_repo']).to be_present
        expect(json_response['http_url_to_repo']).to be_present
        expect(json_response['web_url']).to be_present
        expect(json_response['owner']).to be_a Hash
        expect(json_response['owner']).to be_a Hash
        expect(json_response['name']).to eq(project.name)
        expect(json_response['path']).to be_present
        expect(json_response['issues_enabled']).to be_present
        expect(json_response['merge_requests_enabled']).to be_present
        expect(json_response['wiki_enabled']).to be_present
Toon Claes's avatar
Toon Claes committed
733
        expect(json_response['jobs_enabled']).to be_present
734
735
736
737
738
739
740
        expect(json_response['snippets_enabled']).to be_present
        expect(json_response['container_registry_enabled']).to be_present
        expect(json_response['created_at']).to be_present
        expect(json_response['last_activity_at']).to be_present
        expect(json_response['shared_runners_enabled']).to be_present
        expect(json_response['creator_id']).to be_present
        expect(json_response['namespace']).to be_present
741
742
        expect(json_response['import_status']).to be_present
        expect(json_response).to include("import_error")
743
744
745
        expect(json_response['avatar_url']).to be_nil
        expect(json_response['star_count']).to be_present
        expect(json_response['forks_count']).to be_present
Toon Claes's avatar
Toon Claes committed
746
        expect(json_response['public_jobs']).to be_present
747
        expect(json_response['ci_config_path']).to be_nil
748
749
750
751
752
        expect(json_response['shared_with_groups']).to be_an Array
        expect(json_response['shared_with_groups'].length).to eq(1)
        expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
        expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
        expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
753
        expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
754
755
        expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
      end
756

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
      context 'links exposure' do
        it 'exposes related resources full URIs' do
          get api("/projects/#{project.id}", user)

          links = json_response['_links']

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

        it 'filters related URIs when their feature is not enabled' do
842
          project = create(:project, :public,
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
                           :merge_requests_disabled,
                           :issues_disabled,
                           creator_id: user.id,
                           namespace: user.namespace)

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

          links = json_response['_links']

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

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

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

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

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

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