Read about our upcoming Code of Conduct on this issue

This instance was upgraded to Heptapod 0.28.0 today

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

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

7
8
9
10
11
12
13
14
15
  before do
    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

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

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

23
24
25
26
27
      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
28
  end
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

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

48
describe API::Projects do
49
50
  include ExternalAuthorizationServiceHelpers

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

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

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

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

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

        expect(response).to include_security_headers
      end
123
124
    end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        get api('/projects', user)
221

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

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

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

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

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

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

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

        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

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

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

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

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

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
355
      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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      project = Project.first

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

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

      project = Project.first

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

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

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

602
    it "assigns attributes to project" do
603
      project = attributes_for(:project, {
604
        path: 'camelCasePath',
605
        issues_enabled: false,
winniehell's avatar
winniehell committed
606
        jobs_enabled: false,
607
        merge_requests_enabled: false,
608
        wiki_enabled: false,
609
        resolve_outdated_diff_discussions: false,
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
    it 'sets a project as public' do
635
      project = attributes_for(:project, visibility: 'public')
636

blackst0ne's avatar
blackst0ne committed
637
      post api('/projects', user), params: project
638
639

      expect(json_response['visibility']).to eq('public')
640
641
    end

642
    it 'sets a project as internal' do
643
644
      project = attributes_for(:project, visibility: 'internal')

blackst0ne's avatar
blackst0ne committed
645
      post api('/projects', user), params: project
646
647

      expect(json_response['visibility']).to eq('internal')
648
649
    end

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

blackst0ne's avatar
blackst0ne committed
653
      post api('/projects', user), params: project
654
655

      expect(json_response['visibility']).to eq('private')
656
657
    end

Steve's avatar
Steve committed
658
659
660
    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
661
      post api('/projects', user), params: project
Steve's avatar
Steve committed
662
663
664
665

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

blackst0ne's avatar
blackst0ne committed
669
      post api('/projects', user), params: project
vanadium23's avatar
vanadium23 committed
670
671

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

674
    it 'uploads avatar for project a project' do
675
      project = attributes_for(:project, avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif'))
676

blackst0ne's avatar
blackst0ne committed
677
      post api('/projects', user), params: project
678
679

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

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

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

688
      expect(json_response['resolve_outdated_diff_discussions']).to be_falsey
689
690
    end

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

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

696
      expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
697
698
    end

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

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

704
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
705
706
    end

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

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

712
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
713
714
    end

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

blackst0ne's avatar
blackst0ne committed
718
      post api('/projects', user), params: project
719
720

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

723
724
725
    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
726
      post api('/projects', user), params: project
727
728
729
730

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

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

blackst0ne's avatar
blackst0ne committed
734
      post api('/projects', user), params: project
735
736
737
738

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

739
740
741
    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
742
      post api('/projects', user), params: project
743
744
745
746
747
748
749

      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
750
      post api('/projects', user), params: project
751
752
753
754

      expect(response).to have_gitlab_http_status(400)
    end

755
    it 'ignores import_url when it is nil' do
Sean McGivern's avatar
Sean McGivern committed
756
      project = attributes_for(:project, import_url: nil)
757

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

760
      expect(response).to have_gitlab_http_status(201)
761
762
    end

763
    context 'when a visibility level is restricted' do
764
      let(:project_param) { attributes_for(:project, visibility: 'public') }
765

766
      before do
767
        stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
768
769
      end

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

773
        expect(response).to have_gitlab_http_status(400)
774
775
776
777
778
        expect(json_response['message']['visibility_level'].first).to(
          match('restricted by your GitLab administrator')
        )
      end

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

782
        expect(json_response['visibility']).to eq('public')
783
784
      end
    end
785
786
  end

vanadium23's avatar
vanadium23 committed
787
  describe 'GET /users/:user_id/projects/' do
788
    let!(:public_project) { create(:project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }
vanadium23's avatar
vanadium23 committed
789
790

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

793
      expect(response).to have_gitlab_http_status(404)
vanadium23's avatar
vanadium23 committed
794
795
796
      expect(json_response['message']).to eq('404 User Not Found')
    end

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

800
      expect(response).to have_gitlab_http_status(200)
vanadium23's avatar
vanadium23 committed
801
802
803
804
      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
805

806
807
808
809
    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
810
811
812
813
      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
814

815
    it 'returns projects filtered by minimal access level' do
816
817
818
819
820
      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
821
      get api("/users/#{user4.id}/projects/", user2), params: { min_access_level: 30 }
822
823
824
825
826
827

      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
828
829
830
831
832
833
834
835
836
837
838
839
840

    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
841
842
  end

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

848
      project = Project.last
849
850
851
852
853
854

      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
855
      expect { post api("/projects/user/#{user.id}", admin), params: { path: 'path-project-Foo', name: 'Foo Project' } }
856
        .to change { Project.count }.by(1)
857
      expect(response).to have_gitlab_http_status(201)
858

859
      project = Project.last
860
861
862

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

865
    it 'responds with 400 on failure and not project' do
866
867
      expect { post api("/projects/user/#{user.id}", admin) }
        .not_to change { Project.count }
868

869
      expect(response).to have_gitlab_http_status(400)
Robert Schilling's avatar
Robert Schilling committed
870
      expect(json_response['error']).to eq('name is missing')
Angus MacArthur's avatar
Angus MacArthur committed
871
872
    end

873
    it 'assigns attributes to project' do
Angus MacArthur's avatar
Angus MacArthur committed
874
      project = attributes_for(:project, {
875
876
        issues_enabled: false,
        merge_requests_enabled: false,
877
        wiki_enabled: false,