projects_spec.rb 97.7 KB
Newer Older
Nihad Abbasov's avatar
Nihad Abbasov committed
1
2
require 'spec_helper'

3
4
5
shared_examples 'languages and percentages JSON response' do
  let(:expected_languages) { project.repository.languages.map { |language| language.values_at(:label, :value)}.to_h }

6
  before do
7
8
    allow(DetectRepositoryLanguagesWorker).to receive(:perform_async).and_call_original

9
10
11
12
13
14
15
16
    allow(project.repository).to receive(:languages).and_return(
      [{ value: 66.69, label: "Ruby", color: "#701516", highlight: "#701516" },
       { value: 22.98, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" },
       { value: 7.91, label: "HTML", color: "#e34c26", highlight: "#e34c26" },
       { value: 2.42, label: "CoffeeScript", color: "#244776", highlight: "#244776" }]
    )
  end

17
  context "when the languages haven't been detected yet" do
18
    it 'returns expected language values', :sidekiq_might_not_need_inline do
19
      get api("/projects/#{project.id}/languages", user)
20

21
22
      expect(response).to have_gitlab_http_status(:ok)
      expect(json_response).to eq({})
23

24
25
26
27
28
      get api("/projects/#{project.id}/languages", user)

      expect(response).to have_gitlab_http_status(:ok)
      expect(JSON.parse(response.body)).to eq(expected_languages)
    end
29
  end
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

  context 'when the languages were detected before' do
    before do
      Projects::DetectRepositoryLanguagesService.new(project, project.owner).execute
    end

    it 'returns the detection from the database' do
      # Allow this to happen once, so the expected languages can be determined
      expect(project.repository).to receive(:languages).once

      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
47
48
end

49
describe API::Projects do
50
51
52
  let(:user) { create(:user) }
  let(:user2) { create(:user) }
  let(:user3) { create(:user) }
Angus MacArthur's avatar
Angus MacArthur committed
53
  let(:admin) { create(:admin) }
Imre Farkas's avatar
Imre Farkas committed
54
  let(:project) { create(:project, :repository, namespace: user.namespace) }
55
  let(:project2) { create(:project, namespace: user.namespace) }
56
  let(:project_member) { create(:project_member, :developer, user: user3, project: project) }
57
  let(:user4) { create(:user, username: 'user.with.dot') }
58
59
  let(:project3) do
    create(:project,
60
    :private,
61
    :repository,
62
63
64
65
66
67
    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
68
    builds_enabled: false,
69
    snippets_enabled: false)
70
  end
71
  let(:project_member2) do
72
73
74
    create(:project_member,
    user: user4,
    project: project3,
75
    access_level: ProjectMember::MAINTAINER)
76
77
  end
  let(:project4) do
78
    create(:project,
79
80
81
82
83
84
    name: 'third_project',
    path: 'third_project',
    creator_id: user4.id,
    namespace: user4.namespace)
  end

85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
  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

106
  describe 'GET /projects' do
107
108
    shared_examples_for 'projects response' do
      it 'returns an array of projects' do
blackst0ne's avatar
blackst0ne committed
109
        get api('/projects', current_user), params: filter
110

111
        expect(response).to have_gitlab_http_status(200)
112
        expect(response).to include_pagination_headers
113
114
115
        expect(json_response).to be_an Array
        expect(json_response.map { |p| p['id'] }).to contain_exactly(*projects.map(&:id))
      end
116
117

      it 'returns the proper security headers' do
blackst0ne's avatar
blackst0ne committed
118
        get api('/projects', current_user), params: filter
119
120
121

        expect(response).to include_security_headers
      end
122
123
    end

124
125
    shared_examples_for 'projects response without N + 1 queries' do
      it 'avoids N + 1 queries' do
126
        control = ActiveRecord::QueryRecorder.new do
127
          get api('/projects', current_user)
128
        end
129
130
131
132

        if defined?(additional_project)
          additional_project
        else
133
          create(:project, :public)
134
135
        end

136
137
138
        # 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
139
140
        expect do
          get api('/projects', current_user)
141
        end.not_to exceed_query_limit(control).with_threshold(9)
142
143
144
      end
    end

145
    let!(:public_project) { create(:project, :public, name: 'public_project') }
146

147
148
149
150
151
152
    before do
      project
      project2
      project3
      project4
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
153

154
    context 'when unauthenticated' do
155
      it_behaves_like 'projects response' do
156
157
158
159
160
161
        let(:filter) { { search: project.name } }
        let(:current_user) { user }
        let(:projects) { [project] }
      end

      it_behaves_like 'projects response without N + 1 queries' do
162
        let(:current_user) { nil }
163
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
164
165
    end

Markus Koller's avatar
Markus Koller committed
166
    context 'when authenticated as regular user' do
167
      it_behaves_like 'projects response' do
168
        let(:filter) { {} }
169
170
        let(:current_user) { user }
        let(:projects) { [public_project, project, project2, project3] }
Nihad Abbasov's avatar
Nihad Abbasov committed
171
      end
172

173
174
175
176
177
178
      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
179
          create(:project, :public, group: create(:group))
180
181
182
183
        end

        it_behaves_like 'projects response without N + 1 queries' do
          let(:current_user) { user }
184
          let(:additional_project) { create(:project, :public, group: create(:group)) }
185
186
187
        end
      end

188
      it 'includes the project labels as the tag_list' do
189
        get api('/projects', user)
190

191
        expect(response.status).to eq 200
192
        expect(response).to include_pagination_headers
193
194
        expect(json_response).to be_an Array
        expect(json_response.first.keys).to include('tag_list')
195
      end
196

197
      it 'includes open_issues_count' do
Stan Hu's avatar
Stan Hu committed
198
        get api('/projects', user)
199

Stan Hu's avatar
Stan Hu committed
200
        expect(response.status).to eq 200
201
        expect(response).to include_pagination_headers
Stan Hu's avatar
Stan Hu committed
202
203
204
205
        expect(json_response).to be_an Array
        expect(json_response.first.keys).to include('open_issues_count')
      end

206
207
208
209
210
211
212
213
214
215
      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

216
      it 'does not include open_issues_count if issues are disabled' do
Felipe Artur's avatar
Felipe Artur committed
217
        project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
Stan Hu's avatar
Stan Hu committed
218
219

        get api('/projects', user)
220

Stan Hu's avatar
Stan Hu committed
221
        expect(response.status).to eq 200
222
        expect(response).to include_pagination_headers
Stan Hu's avatar
Stan Hu committed
223
        expect(json_response).to be_an Array
224
        expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
Stan Hu's avatar
Stan Hu committed
225
226
      end

227
228
229
230
231
232
233
234
235
236
237
238
239
      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

240
241
242
      it "does not include statistics by default" do
        get api('/projects', user)

243
        expect(response).to have_gitlab_http_status(200)
244
        expect(response).to include_pagination_headers
245
246
        expect(json_response).to be_an Array
        expect(json_response.first).not_to include('statistics')
Stan Hu's avatar
Stan Hu committed
247
248
      end

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

252
        expect(response).to have_gitlab_http_status(200)
253
        expect(response).to include_pagination_headers
254
255
        expect(json_response).to be_an Array
        expect(json_response.first).to include 'statistics'
Stan Hu's avatar
Stan Hu committed
256
257
      end

J.D. Bean's avatar
J.D. Bean committed
258
259
260
261
262
263
264
265
266
267
      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
268
        get api('/projects', user), params: { license: true }
J.D. Bean's avatar
J.D. Bean committed
269
270
271
272
273
274
275

        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

276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
      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

301
      context 'and with simple=true' do
tiagonbotelho's avatar
tiagonbotelho committed
302
        it 'returns a simplified version of all the projects' do
303
304
          expected_keys = %w(
            id description default_branch tag_list
Imre Farkas's avatar
Imre Farkas committed
305
            ssh_url_to_repo http_url_to_repo web_url readme_url
306
307
308
309
            name name_with_namespace
            path path_with_namespace
            star_count forks_count
            created_at last_activity_at
310
            avatar_url namespace
311
          )
312

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

315
          expect(response).to have_gitlab_http_status(200)
316
          expect(response).to include_pagination_headers
317
          expect(json_response).to be_an Array
318
          expect(json_response.first.keys).to match_array expected_keys
319
320
321
        end
      end

322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
      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

355
      context 'and using search' do
356
357
358
359
360
361
        it_behaves_like 'projects response' do
          let(:filter) { { search: project.name } }
          let(:current_user) { user }
          let(:projects) { [project] }
        end
      end
362

363
      context 'and membership=true' do
364
        it_behaves_like 'projects response' do
365
          let(:filter) { { membership: true } }
366
367
          let(:current_user) { user }
          let(:projects) { [project, project2, project3] }
368
369
370
        end
      end

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

375
          expect(response).to have_gitlab_http_status(200)
376
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
377
          expect(json_response).to be_an Array
378
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id, project2.id, project3.id)
Josh Frye's avatar
Josh Frye committed
379
380
        end

381
        it 'filters based on internal visibility param' do
382
383
          project2.update_attribute(:visibility_level, Gitlab::VisibilityLevel::INTERNAL)

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

386
          expect(response).to have_gitlab_http_status(200)
387
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
388
          expect(json_response).to be_an Array
389
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project2.id)
Josh Frye's avatar
Josh Frye committed
390
391
        end

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

395
          expect(response).to have_gitlab_http_status(200)
396
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
397
          expect(json_response).to be_an Array
398
          expect(json_response.map { |p| p['id'] }).to contain_exactly(public_project.id)
Josh Frye's avatar
Josh Frye committed
399
400
401
        end
      end

402
403
404
405
406
407
408
409
410
411
412
413
414
      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

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

419
          expect(response).to have_gitlab_http_status(200)
420
          expect(response).to include_pagination_headers
421
422
          expect(json_response).to be_an Array
          expect(json_response.first['id']).to eq(project3.id)
423
424
        end
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
425

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

430
          expect(response).to have_gitlab_http_status(200)
431
          expect(response).to include_pagination_headers
432
433
434
          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)
435
        end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
436
437
      end

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

441
        before do
442
          project_member
Lin Jen-Shin's avatar
Lin Jen-Shin committed
443
          user3.update(starred_projects: [project, project2, project3, public_project])
444
        end
Marin Jankovski's avatar
Marin Jankovski committed
445

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

449
          expect(response).to have_gitlab_http_status(200)
450
          expect(response).to include_pagination_headers
451
452
          expect(json_response).to be_an Array
          expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
453
        end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
454
455
      end

456
      context 'and with all query parameters' do
457
        let!(:project5) { create(:project, :public, path: 'gitlab5', namespace: create(:namespace)) }
458
        let!(:project6) { create(:project, :public, namespace: user.namespace) }
459
460
461
        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') }
462

463
        before do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
464
          user.update(starred_projects: [project5, project7, project8, project9])
465
        end
466

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

471
            expect(response).to have_gitlab_http_status(200)
472
473
474
475
476
477
            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
478

479
        context 'including membership filter' do
480
481
482
483
          before do
            create(:project_member,
                   user: user,
                   project: project5,
484
                   access_level: ProjectMember::MAINTAINER)
485
          end
486

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

490
            expect(response).to have_gitlab_http_status(200)
491
492
493
            expect(response).to include_pagination_headers
            expect(json_response).to be_an Array
            expect(json_response.size).to eq(2)
494
            expect(json_response.map { |project| project['id'] }).to contain_exactly(project5.id, project7.id)
495
          end
496
        end
497
      end
498
499
500

      context 'and with min_access_level' do
        before do
501
          project2.add_maintainer(user2)
502
503
504
505
          project3.add_developer(user2)
          project4.add_reporter(user2)
        end

506
        it 'returns an array of projects the user has at least developer access' do
blackst0ne's avatar
blackst0ne committed
507
          get api('/projects', user2), params: { min_access_level: 30 }
508

509
510
511
512
513
514
          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
515
    end
516

517
    context 'when authenticated as a different user' do
518
      it_behaves_like 'projects response' do
519
        let(:filter) { {} }
520
521
522
        let(:current_user) { user2 }
        let(:projects) { [public_project] }
      end
523
524
525
526
527
528
529
530
531
532
533
534
535

      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
536
537
    end

538
539
    context 'when authenticated as admin' do
      it_behaves_like 'projects response' do
540
        let(:filter) { {} }
541
542
543
        let(:current_user) { admin }
        let(:projects) { Project.all }
      end
544
545
546
    end
  end

547
548
  describe 'POST /projects' do
    context 'maximum number of projects reached' do
549
      it 'does not create new project and respond with 403' do
550
        allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0)
blackst0ne's avatar
blackst0ne committed
551
        expect { post api('/projects', user2), params: { name: 'foo' } }
552
          .to change {Project.count}.by(0)
553
        expect(response).to have_gitlab_http_status(403)
554
555
556
      end
    end

557
    it 'creates new project without path but with name and returns 201' do
blackst0ne's avatar
blackst0ne committed
558
      expect { post api('/projects', user), params: { name: 'Foo Project' } }
559
        .to change { Project.count }.by(1)
560
      expect(response).to have_gitlab_http_status(201)
561
562
563
564
565
566
567
568

      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
569
      expect { post api('/projects', user), params: { path: 'foo_project' } }
570
        .to change { Project.count }.by(1)
571
      expect(response).to have_gitlab_http_status(201)
572
573
574
575
576
577
578

      project = Project.first

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

579
    it 'creates new project with name and path and returns 201' do
blackst0ne's avatar
blackst0ne committed
580
      expect { post api('/projects', user), params: { path: 'path-project-Foo', name: 'Foo Project' } }
581
        .to change { Project.count }.by(1)
582
      expect(response).to have_gitlab_http_status(201)
583
584
585
586

      project = Project.first

      expect(project.name).to eq('Foo Project')
587
      expect(project.path).to eq('path-project-Foo')
588
589
    end

590
    it 'creates last project before reaching project limit' do
591
      allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1)
blackst0ne's avatar
blackst0ne committed
592
      post api('/projects', user2), params: { name: 'foo' }
593
      expect(response).to have_gitlab_http_status(201)
594
595
    end

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

601
    it "assigns attributes to project" do
602
      project = attributes_for(:project, {
603
        path: 'camelCasePath',
604
        issues_enabled: false,
winniehell's avatar
winniehell committed
605
        jobs_enabled: false,
606
        merge_requests_enabled: false,
607
        wiki_enabled: false,
608
        resolve_outdated_diff_discussions: false,
609
        remove_source_branch_after_merge: true,
610
        only_allow_merge_if_pipeline_succeeds: false,
611
        request_access_enabled: true,
612
        only_allow_merge_if_all_discussions_are_resolved: false,
613
614
        ci_config_path: 'a/custom/path',
        merge_method: 'ff'
Alex Denisov's avatar
Alex Denisov committed
615
616
      })

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

619
      expect(response).to have_gitlab_http_status(201)
winniehell's avatar
winniehell committed
620

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

624
        expect(json_response[k.to_s]).to eq(v)
Alex Denisov's avatar
Alex Denisov committed
625
      end
Felipe Artur's avatar
Felipe Artur committed
626
627
628
629
630
631

      # 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)
632
    end
633

634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
    it 'creates a project using a template' do
      expect { post api('/projects', user), params: { template_name: 'rails', name: 'rails-test' } }
        .to change { Project.count }.by(1)

      expect(response).to have_gitlab_http_status(201)

      project = Project.find(json_response['id'])
      expect(project).to be_saved
      expect(project.import_type).to eq('gitlab_project')
    end

    it 'returns 400 for an invalid template' do
      expect { post api('/projects', user), params: { template_name: 'unknown', name: 'rails-test' } }
        .not_to change { Project.count }

      expect(response).to have_gitlab_http_status(400)
      expect(json_response['message']['template_name']).to eq(["'unknown' is unknown or invalid"])
    end

    it 'disallows creating a project with an import_url and template' do
      project_params = { import_url: 'http://example.com', template_name: 'rails', name: 'rails-test' }
      expect { post api('/projects', user), params: project_params }
        .not_to change {  Project.count }

      expect(response).to have_gitlab_http_status(400)
    end

661
    it 'sets a project as public' do
662
      project = attributes_for(:project, visibility: 'public')
663

blackst0ne's avatar
blackst0ne committed
664
      post api('/projects', user), params: project
665
666

      expect(json_response['visibility']).to eq('public')
667
668
    end

669
    it 'sets a project as internal' do
670
671
      project = attributes_for(:project, visibility: 'internal')

blackst0ne's avatar
blackst0ne committed
672
      post api('/projects', user), params: project
673
674

      expect(json_response['visibility']).to eq('internal')
675
676
    end

677
    it 'sets a project as private' do
678
679
      project = attributes_for(:project, visibility: 'private')

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

      expect(json_response['visibility']).to eq('private')
683
684
    end

Steve's avatar
Steve committed
685
686
687
    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
688
      post api('/projects', user), params: project
Steve's avatar
Steve committed
689
690
691
692

      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
693
694
695
    it 'sets tag list to a project' do
      project = attributes_for(:project, tag_list: %w[tagFirst tagSecond])

blackst0ne's avatar
blackst0ne committed
696
      post api('/projects', user), params: project
vanadium23's avatar
vanadium23 committed
697
698

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

701
    it 'uploads avatar for project a project' do
702
      project = attributes_for(:project, avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif'))
703

blackst0ne's avatar
blackst0ne committed
704
      post api('/projects', user), params: project
705
706

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

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

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

715
      expect(json_response['resolve_outdated_diff_discussions']).to be_falsey
716
717
    end

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

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

723
      expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
724
725
    end

726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
    it 'sets a project as not removing source branches' do
      project = attributes_for(:project, remove_source_branch_after_merge: false)

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

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

    it 'sets a project as removing source branches' do
      project = attributes_for(:project, remove_source_branch_after_merge: true)

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

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

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

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

747
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
748
749
    end

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

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

755
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
756
757
    end

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

blackst0ne's avatar
blackst0ne committed
761
      post api('/projects', user), params: project
762
763

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

766
767
768
    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
769
      post api('/projects', user), params: project
770
771
772
773

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

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

blackst0ne's avatar
blackst0ne committed
777
      post api('/projects', user), params: project
778
779
780
781

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

782
783
784
    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
785
      post api('/projects', user), params: project
786
787
788
789
790
791
792

      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
793
      post api('/projects', user), params: project
794
795
796
797

      expect(response).to have_gitlab_http_status(400)
    end

798
    it 'ignores import_url when it is nil' do
Sean McGivern's avatar
Sean McGivern committed
799
      project = attributes_for(:project, import_url: nil)
800

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

803
      expect(response).to have_gitlab_http_status(201)
804
805
    end

806
    context 'when a visibility level is restricted' do
807
      let(:project_param) { attributes_for(:project, visibility: 'public') }
808

809
      before do
810
        stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
811
812
      end

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

816
        expect(response).to have_gitlab_http_status(400)
817
818
819
820
821
        expect(json_response['message']['visibility_level'].first).to(
          match('restricted by your GitLab administrator')
        )
      end

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

825
        expect(json_response['visibility']).to eq('public')
826
827
      end
    end
828
829
  end

vanadium23's avatar
vanadium23 committed
830
  describe 'GET /users/:user_id/projects/' do
831
    let!(:public_project) { create(:project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }
vanadium23's avatar
vanadium23 committed
832
833

    it 'returns error when user not found' do
834
      get api('/users/0/projects/')
vanadium23's avatar
vanadium23 committed
835

836
      expect(response).to have_gitlab_http_status(404)
vanadium23's avatar
vanadium23 committed
837
838
839
      expect(json_response['message']).to eq('404 User Not Found')
    end

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

843
      expect(response).to have_gitlab_http_status(200)
vanadium23's avatar
vanadium23 committed
844
845
846
847
      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
848

849
850
851
852
    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
853
854
855
856
      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
857

858
    it 'returns projects filtered by minimal access level' do
859
860
861
862
863
      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
864
      get api("/users/#{user4.id}/projects/", user2), params: { min_access_level: 30 }
865
866
867
868
869
870

      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
871
872
873
874
875
876
877
878
879
880
881
882
883

    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