Read about our upcoming Code of Conduct on this issue

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

4
5
6
7
8
9
10
11
12
13
14
15
shared_examples 'languages and percentages JSON response' do
  let(:expected_languages) { project.repository.languages.map { |language| language.values_at(:label, :value)}.to_h }

  it 'returns expected language values' do
    get api("/projects/#{project.id}/languages", user)

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

16
describe API::Projects do
17
18
19
  let(:user) { create(:user) }
  let(:user2) { create(:user) }
  let(:user3) { create(:user) }
Angus MacArthur's avatar
Angus MacArthur committed
20
  let(:admin) { create(:admin) }
Imre Farkas's avatar
Imre Farkas committed
21
  let(:project) { create(:project, :repository, namespace: user.namespace) }
22
  let(:project2) { create(:project, namespace: user.namespace) }
23
  let(:project_member) { create(:project_member, :developer, user: user3, project: project) }
24
  let(:user4) { create(:user, username: 'user.with.dot') }
25
26
  let(:project3) do
    create(:project,
27
    :private,
28
    :repository,
29
30
31
32
33
34
    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
35
    builds_enabled: false,
36
    snippets_enabled: false)
37
  end
38
  let(:project_member2) do
39
40
41
    create(:project_member,
    user: user4,
    project: project3,
42
    access_level: ProjectMember::MAINTAINER)
43
44
  end
  let(:project4) do
45
    create(:project,
46
47
48
49
50
51
    name: 'third_project',
    path: 'third_project',
    creator_id: user4.id,
    namespace: user4.namespace)
  end

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
  shared_context 'with language detection' do
    let(:ruby) { create(:programming_language, name: 'Ruby') }
    let(:javascript) { create(:programming_language, name: 'JavaScript') }
    let(:html) { create(:programming_language, name: 'HTML') }

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

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

73
  describe 'GET /projects' do
74
75
    shared_examples_for 'projects response' do
      it 'returns an array of projects' do
blackst0ne's avatar
blackst0ne committed
76
        get api('/projects', current_user), params: filter
77

78
        expect(response).to have_gitlab_http_status(200)
79
        expect(response).to include_pagination_headers
80
81
82
        expect(json_response).to be_an Array
        expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id))
      end
83
84

      it 'returns the proper security headers' do
blackst0ne's avatar
blackst0ne committed
85
        get api('/projects', current_user), params: filter
86
87
88

        expect(response).to include_security_headers
      end
89
90
    end

91
92
    shared_examples_for 'projects response without N + 1 queries' do
      it 'avoids N + 1 queries' do
93
        control = ActiveRecord::QueryRecorder.new do
94
          get api('/projects', current_user)
95
        end
96
97
98
99

        if defined?(additional_project)
          additional_project
        else
100
          create(:project, :public)
101
102
        end

103
104
105
        # TODO: We're currently querying to detect if a project is a fork
        # in 2 ways. Lower this back to 8 when `ForkedProjectLink` relation is
        # removed
106
107
        expect do
          get api('/projects', current_user)
108
        end.not_to exceed_query_limit(control).with_threshold(9)
109
110
111
      end
    end

112
    let!(:public_project) { create(:project, :public, name: 'public_project') }
113
114
115
116
117
118
    before do
      project
      project2
      project3
      project4
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
119

120
    context 'when unauthenticated' do
121
      it_behaves_like 'projects response' do
122
123
124
125
126
127
        let(:filter) { { search: project.name } }
        let(:current_user) { user }
        let(:projects) { [project] }
      end

      it_behaves_like 'projects response without N + 1 queries' do
128
        let(:current_user) { nil }
129
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
130
131
    end

Markus Koller's avatar
Markus Koller committed
132
    context 'when authenticated as regular user' do
133
      it_behaves_like 'projects response' do
134
        let(:filter) { {} }
135
136
        let(:current_user) { user }
        let(:projects) { [public_project, project, project2, project3] }
Nihad Abbasov's avatar
Nihad Abbasov committed
137
      end
138

139
140
141
142
143
144
      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
145
          create(:project, :public, group: create(:group))
146
147
148
149
        end

        it_behaves_like 'projects response without N + 1 queries' do
          let(:current_user) { user }
150
          let(:additional_project) { create(:project, :public, group: create(:group)) }
151
152
153
        end
      end

154
      it 'includes the project labels as the tag_list' do
155
        get api('/projects', user)
156

157
        expect(response.status).to eq 200
158
        expect(response).to include_pagination_headers
159
160
        expect(json_response).to be_an Array
        expect(json_response.first.keys).to include('tag_list')
161
      end
162

163
      it 'includes open_issues_count' do
Stan Hu's avatar
Stan Hu committed
164
        get api('/projects', user)
165

Stan Hu's avatar
Stan Hu committed
166
        expect(response.status).to eq 200
167
        expect(response).to include_pagination_headers
Stan Hu's avatar
Stan Hu committed
168
169
170
171
        expect(json_response).to be_an Array
        expect(json_response.first.keys).to include('open_issues_count')
      end

172
173
174
175
176
177
178
179
180
181
      it 'does not include projects marked for deletion' do
        project.update(pending_delete: true)

        get api('/projects', user)

        expect(response).to have_gitlab_http_status(200)
        expect(json_response).to be_an Array
        expect(json_response.map { |p| p['id'] }).not_to include(project.id)
      end

182
      it 'does not include open_issues_count if issues are disabled' do
Felipe Artur's avatar
Felipe Artur committed
183
        project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
Stan Hu's avatar
Stan Hu committed
184
185

        get api('/projects', user)
186

Stan Hu's avatar
Stan Hu committed
187
        expect(response.status).to eq 200
188
        expect(response).to include_pagination_headers
Stan Hu's avatar
Stan Hu committed
189
        expect(json_response).to be_an Array
190
        expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
Stan Hu's avatar
Stan Hu committed
191
192
      end

193
194
195
196
197
198
199
200
201
202
203
204
205
      context 'and with_issues_enabled=true' do
        it 'only returns projects with issues enabled' do
          project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)

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

          expect(response.status).to eq 200
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.map { |p| p['id'] }).not_to include(project.id)
        end
      end

206
207
208
      it "does not include statistics by default" do
        get api('/projects', user)

209
        expect(response).to have_gitlab_http_status(200)
210
        expect(response).to include_pagination_headers
211
212
        expect(json_response).to be_an Array
        expect(json_response.first).not_to include('statistics')
Stan Hu's avatar
Stan Hu committed
213
214
      end

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

218
        expect(response).to have_gitlab_http_status(200)
219
        expect(response).to include_pagination_headers
220
221
        expect(json_response).to be_an Array
        expect(json_response.first).to include 'statistics'
Stan Hu's avatar
Stan Hu committed
222
223
      end

J.D. Bean's avatar
J.D. Bean committed
224
225
226
227
228
229
230
231
232
233
      it "does not include license by default" do
        get api('/projects', user)

        expect(response).to have_gitlab_http_status(200)
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response.first).not_to include('license', 'license_url')
      end

      it "does not include license if requested" do
blackst0ne's avatar
blackst0ne committed
234
        get api('/projects', user), params: { license: true }
J.D. Bean's avatar
J.D. Bean committed
235
236
237
238
239
240
241

        expect(response).to have_gitlab_http_status(200)
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response.first).not_to include('license', 'license_url')
      end

242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
      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

267
      context 'and with simple=true' do
tiagonbotelho's avatar
tiagonbotelho committed
268
        it 'returns a simplified version of all the projects' do
269
270
          expected_keys = %w(
            id description default_branch tag_list
Imre Farkas's avatar
Imre Farkas committed
271
            ssh_url_to_repo http_url_to_repo web_url readme_url
272
273
274
275
            name name_with_namespace
            path path_with_namespace
            star_count forks_count
            created_at last_activity_at
276
            avatar_url namespace
277
          )
278

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

281
          expect(response).to have_gitlab_http_status(200)
282
          expect(response).to include_pagination_headers
283
          expect(json_response).to be_an Array
284
          expect(json_response.first.keys).to match_array expected_keys
285
286
287
        end
      end

288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
      context 'and using archived' do
        let!(:archived_project) { create(:project, creator_id: user.id, namespace: user.namespace, archived: true) }

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

          expect(response).to have_gitlab_http_status(200)
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.length).to eq(Project.public_or_visible_to_user(user).where(archived: true).size)
          expect(json_response.map { |project| project['id'] }).to include(archived_project.id)
        end

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

          expect(response).to have_gitlab_http_status(200)
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.length).to eq(Project.public_or_visible_to_user(user).where(archived: false).size)
          expect(json_response.map { |project| project['id'] }).not_to include(archived_project.id)
        end

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

          expect(response).to have_gitlab_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(*Project.public_or_visible_to_user(user).pluck(:id))
        end
      end

321
      context 'and using search' do
322
323
324
325
326
327
        it_behaves_like 'projects response' do
          let(:filter) { { search: project.name } }
          let(:current_user) { user }
          let(:projects) { [project] }
        end
      end
328

329
      context 'and membership=true' do
330
        it_behaves_like 'projects response' do
331
          let(:filter) { { membership: true } }
332
333
          let(:current_user) { user }
          let(:projects) { [project, project2, project3] }
334
335
336
        end
      end

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

341
          expect(response).to have_gitlab_http_status(200)
342
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
343
          expect(json_response).to be_an Array
344
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id, project2.id, project3.id)
Josh Frye's avatar
Josh Frye committed
345
346
        end

347
        it 'filters based on internal visibility param' do
348
349
          project2.update_attribute(:visibility_level, Gitlab::VisibilityLevel::INTERNAL)

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

352
          expect(response).to have_gitlab_http_status(200)
353
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
354
          expect(json_response).to be_an Array
355
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project2.id)
Josh Frye's avatar
Josh Frye committed
356
357
        end

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

361
          expect(response).to have_gitlab_http_status(200)
362
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
363
          expect(json_response).to be_an Array
364
          expect(json_response.map { |p| p['id'] }).to contain_exactly(public_project.id)
Josh Frye's avatar
Josh Frye committed
365
366
367
        end
      end

368
369
370
371
372
373
374
375
376
377
378
379
380
      context 'and using the programming language filter' do
        include_context 'with language detection'

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

          expect(response).to have_gitlab_http_status(200)
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project3.id)
        end
      end

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

385
          expect(response).to have_gitlab_http_status(200)
386
          expect(response).to include_pagination_headers
387
388
          expect(json_response).to be_an Array
          expect(json_response.first['id']).to eq(project3.id)
389
390
        end
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
391

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

396
          expect(response).to have_gitlab_http_status(200)
397
          expect(response).to include_pagination_headers
398
399
400
          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)
401
        end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
402
403
      end

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

407
        before do
408
          project_member
Lin Jen-Shin's avatar
Lin Jen-Shin committed
409
          user3.update(starred_projects: [project, project2, project3, public_project])
410
        end
Marin Jankovski's avatar
Marin Jankovski committed
411

412
        it 'returns the starred projects viewable by the user' do
blackst0ne's avatar
blackst0ne committed
413
          get api('/projects', user3), params: { starred: true }
Markus Koller's avatar
Markus Koller committed
414

415
          expect(response).to have_gitlab_http_status(200)
416
          expect(response).to include_pagination_headers
417
418
          expect(json_response).to be_an Array
          expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
419
        end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
420
421
      end

422
      context 'and with all query parameters' do
423
        let!(:project5) { create(:project, :public, path: 'gitlab5', namespace: create(:namespace)) }
424
        let!(:project6) { create(:project, :public, namespace: user.namespace) }
425
426
427
        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') }
428

429
        before do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
430
          user.update(starred_projects: [project5, project7, project8, project9])
431
        end
432

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

437
            expect(response).to have_gitlab_http_status(200)
438
439
440
441
442
443
            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
444

445
        context 'including membership filter' do
446
447
448
449
          before do
            create(:project_member,
                   user: user,
                   project: project5,
450
                   access_level: ProjectMember::MAINTAINER)
451
          end
452

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

456
            expect(response).to have_gitlab_http_status(200)
457
458
459
            expect(response).to include_pagination_headers
            expect(json_response).to be_an Array
            expect(json_response.size).to eq(2)
460
            expect(json_response.map { |project| project['id'] }).to contain_exactly(project5.id, project7.id)
461
          end
462
        end
463
      end
464
465
466

      context 'and with min_access_level' do
        before do
467
          project2.add_maintainer(user2)
468
469
470
471
472
          project3.add_developer(user2)
          project4.add_reporter(user2)
        end

        it 'returns an array of groups the user has at least developer access' do
blackst0ne's avatar
blackst0ne committed
473
          get api('/projects', user2), params: { min_access_level: 30 }
474
475
476
477
478
479
          expect(response).to have_gitlab_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(project2.id, project3.id)
        end
      end
480
    end
481

482
    context 'when authenticated as a different user' do
483
      it_behaves_like 'projects response' do
484
        let(:filter) { {} }
485
486
487
        let(:current_user) { user2 }
        let(:projects) { [public_project] }
      end
488
489
490
491
492
493
494
495
496
497
498
499
500

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

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

          expect(response.status).to eq 200
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.map { |p| p['id'] }).not_to include(project.id)
        end
      end
501
502
    end

503
504
    context 'when authenticated as admin' do
      it_behaves_like 'projects response' do
505
        let(:filter) { {} }
506
507
508
        let(:current_user) { admin }
        let(:projects) { Project.all }
      end
509
510
511
    end
  end

512
513
  describe 'POST /projects' do
    context 'maximum number of projects reached' do
514
      it 'does not create new project and respond with 403' do
515
        allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0)
blackst0ne's avatar
blackst0ne committed
516
        expect { post api('/projects', user2), params: { name: 'foo' } }
517
          .to change {Project.count}.by(0)
518
        expect(response).to have_gitlab_http_status(403)
519
520
521
      end
    end

522
    it 'creates new project without path but with name and returns 201' do
blackst0ne's avatar
blackst0ne committed
523
      expect { post api('/projects', user), params: { name: 'Foo Project' } }
524
        .to change { Project.count }.by(1)
525
      expect(response).to have_gitlab_http_status(201)
526
527
528
529
530
531
532
533

      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
blackst0ne's avatar
blackst0ne committed
534
      expect { post api('/projects', user), params: { path: 'foo_project' } }
535
        .to change { Project.count }.by(1)
536
      expect(response).to have_gitlab_http_status(201)
537
538
539
540
541
542
543

      project = Project.first

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

544
    it 'creates new project with name and path and returns 201' do
blackst0ne's avatar
blackst0ne committed
545
      expect { post api('/projects', user), params: { path: 'path-project-Foo', name: 'Foo Project' } }
546
        .to change { Project.count }.by(1)
547
      expect(response).to have_gitlab_http_status(201)
548
549
550
551

      project = Project.first

      expect(project.name).to eq('Foo Project')
552
      expect(project.path).to eq('path-project-Foo')
553
554
    end

555
    it 'creates last project before reaching project limit' do
556
      allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1)
blackst0ne's avatar
blackst0ne committed
557
      post api('/projects', user2), params: { name: 'foo' }
558
      expect(response).to have_gitlab_http_status(201)
559
560
    end

561
    it 'does not create new project without name or path and returns 400' do
562
      expect { post api('/projects', user) }.not_to change { Project.count }
563
      expect(response).to have_gitlab_http_status(400)
564
    end
Alex Denisov's avatar
Alex Denisov committed
565

566
    it "assigns attributes to project" do
567
      project = attributes_for(:project, {
568
        path: 'camelCasePath',
569
        issues_enabled: false,
winniehell's avatar
winniehell committed
570
        jobs_enabled: false,
571
        merge_requests_enabled: false,
572
        wiki_enabled: false,
573
        resolve_outdated_diff_discussions: false,
574
        only_allow_merge_if_pipeline_succeeds: false,
575
        request_access_enabled: true,
576
        only_allow_merge_if_all_discussions_are_resolved: false,
577
578
        ci_config_path: 'a/custom/path',
        merge_method: 'ff'
Alex Denisov's avatar
Alex Denisov committed
579
580
      })

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

583
      expect(response).to have_gitlab_http_status(201)
winniehell's avatar
winniehell committed
584

585
      project.each_pair do |k, v|
Nick Thomas's avatar
Nick Thomas committed
586
        next if %i[has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled storage_version].include?(k)
587

588
        expect(json_response[k.to_s]).to eq(v)
Alex Denisov's avatar
Alex Denisov committed
589
      end
Felipe Artur's avatar
Felipe Artur committed
590
591
592
593
594
595

      # 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)
596
    end
597

598
    it 'sets a project as public' do
599
      project = attributes_for(:project, visibility: 'public')
600

blackst0ne's avatar
blackst0ne committed
601
      post api('/projects', user), params: project
602
603

      expect(json_response['visibility']).to eq('public')
604
605
    end

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

blackst0ne's avatar
blackst0ne committed
609
      post api('/projects', user), params: project
610
611

      expect(json_response['visibility']).to eq('internal')
612
613
    end

614
    it 'sets a project as private' do
615
616
      project = attributes_for(:project, visibility: 'private')

blackst0ne's avatar
blackst0ne committed
617
      post api('/projects', user), params: project
618
619

      expect(json_response['visibility']).to eq('private')
620
621
    end

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

blackst0ne's avatar
blackst0ne committed
625
      post api('/projects', user), params: project
Steve's avatar
Steve committed
626
627
628
629

      expect(json_response['readme_url']).to eql("#{Gitlab.config.gitlab.url}/#{json_response['namespace']['full_path']}/somewhere/blob/master/README.md")
    end

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

blackst0ne's avatar
blackst0ne committed
633
      post api('/projects', user), params: project
vanadium23's avatar
vanadium23 committed
634
635

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

638
    it 'uploads avatar for project a project' do
639
      project = attributes_for(:project, avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif'))
640

blackst0ne's avatar
blackst0ne committed
641
      post api('/projects', user), params: project
642
643

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

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

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

652
      expect(json_response['resolve_outdated_diff_discussions']).to be_falsey
653
654
    end

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

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

660
      expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
661
662
    end

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

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

668
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
669
670
    end

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

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

676
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
677
678
    end

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

blackst0ne's avatar
blackst0ne committed
682
      post api('/projects', user), params: project
683
684

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

687
688
689
    it 'sets a project as allowing merge if only_allow_merge_if_all_discussions_are_resolved is nil' do
      project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: nil)

blackst0ne's avatar
blackst0ne committed
690
      post api('/projects', user), params: project
691
692
693
694

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

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

blackst0ne's avatar
blackst0ne committed
698
      post api('/projects', user), params: project
699
700
701
702

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

703
704
705
    it 'sets the merge method of a project to rebase merge' do
      project = attributes_for(:project, merge_method: 'rebase_merge')

blackst0ne's avatar
blackst0ne committed
706
      post api('/projects', user), params: project
707
708
709
710
711
712
713

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

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

blackst0ne's avatar
blackst0ne committed
714
      post api('/projects', user), params: project
715
716
717
718

      expect(response).to have_gitlab_http_status(400)
    end

719
    it 'ignores import_url when it is nil' do
Sean McGivern's avatar
Sean McGivern committed
720
      project = attributes_for(:project, import_url: nil)
721

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

724
      expect(response).to have_gitlab_http_status(201)
725
726
    end

727
    context 'when a visibility level is restricted' do
728
      let(:project_param) { attributes_for(:project, visibility: 'public') }
729

730
      before do
731
        stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
732
733
      end

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

737
        expect(response).to have_gitlab_http_status(400)
738
739
740
741
742
        expect(json_response['message']['visibility_level'].first).to(
          match('restricted by your GitLab administrator')
        )
      end

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

746
        expect(json_response['visibility']).to eq('public')
747
748
      end
    end
749
750
  end

vanadium23's avatar
vanadium23 committed
751
  describe 'GET /users/:user_id/projects/' do
752
    let!(:public_project) { create(:project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }
vanadium23's avatar
vanadium23 committed
753
754
755
756

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

757
      expect(response).to have_gitlab_http_status(404)
vanadium23's avatar
vanadium23 committed
758
759
760
      expect(json_response['message']).to eq('404 User Not Found')
    end

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

764
      expect(response).to have_gitlab_http_status(200)
vanadium23's avatar
vanadium23 committed
765
766
767
768
      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
769

770
771
772
773
    it 'returns projects filtered by username' do
      get api("/users/#{user4.username}/projects/", user)

      expect(response).to have_gitlab_http_status(200)
vanadium23's avatar
vanadium23 committed
774
775
776
777
      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
778

779
    it 'returns projects filtered by minimal access level' do
780
781
782
783
784
      private_project1 = create(:project, :private, name: 'private_project1', creator_id: user4.id, namespace: user4.namespace)
      private_project2 = create(:project, :private, name: 'private_project2', creator_id: user4.id, namespace: user4.namespace)
      private_project1.add_developer(user2)
      private_project2.add_reporter(user2)

blackst0ne's avatar
blackst0ne committed
785
      get api("/users/#{user4.id}/projects/", user2), params: { min_access_level: 30 }
786
787
788
789
790
791

      expect(response).to have_gitlab_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(private_project1.id)
    end
792
793
794
795
796
797
798
799
800
801
802
803
804

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

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

        expect(response).to have_gitlab_http_status(200)
        expect(response).to include_pagination_headers
        expect(json_response).to be_an Array
        expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id)
      end
    end
vanadium23's avatar
vanadium23 committed
805
806
  end

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

812
      project = Project.last
813
814
815
816
817
818

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

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

823
      project = Project.last
824
825
826

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

829
    it 'responds with 400 on failure and not project' do
830
831
      expect { post api("/projects/user/#{user.id}", admin) }
        .not_to change { Project.count }
832

833
      expect(response).to have_gitlab_http_status(400)
Robert Schilling's avatar
Robert Schilling committed
834
      expect(json_response['error']).to eq('name is missing')
Angus MacArthur's avatar
Angus MacArthur committed
835
836
    end

837
    it 'assigns attributes to project' do
Angus MacArthur's avatar
Angus MacArthur committed
838
      project = attributes_for(:project, {
839
840
        issues_enabled: false,
        merge_requests_enabled: false,
841
        wiki_enabled: false,
842
843
        request_access_enabled: true,
        jobs_enabled: true
Angus MacArthur's avatar
Angus MacArthur committed
844
845
      })

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

848
      expect(response).to have_gitlab_http_status(201)
Nick Thomas's avatar
Nick Thomas committed
849

850
      project.each_pair do |k, v|
Nick Thomas's avatar
Nick Thomas committed
851
        next if %i[has_external_issue_tracker path storage_version].include?(k)
852

853
        expect(json_response[k.to_s]).to eq(v)
Angus MacArthur's avatar
Angus MacArthur committed
854
855
      end
    end
856

857
    it 'sets a project as public' do
858
      project = attributes_for(:project, visibility: 'public')
859

blackst0ne's avatar
blackst0ne committed
860
      post api("/projects/user/#{user.id}", admin), params: project
Robert Schilling's avatar
Robert Schilling committed
861

862
      expect(response).to have_gitlab_http_status(201)
863
      expect(json_response['visibility']).to eq('public')