Read about our upcoming Code of Conduct on this issue

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Stan Hu's avatar
Stan Hu committed
127
        expect(response.status).to eq 200
128
        expect(response).to include_pagination_headers
Stan Hu's avatar
Stan Hu committed
129
130
131
132
        expect(json_response).to be_an Array
        expect(json_response.first.keys).to include('open_issues_count')
      end

133
      it 'does not include open_issues_count if issues are disabled' do
Felipe Artur's avatar
Felipe Artur committed
134
        project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
Stan Hu's avatar
Stan Hu committed
135
136

        get api('/projects', user)
137

Stan Hu's avatar
Stan Hu committed
138
        expect(response.status).to eq 200
139
        expect(response).to include_pagination_headers
Stan Hu's avatar
Stan Hu committed
140
        expect(json_response).to be_an Array
141
        expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
Stan Hu's avatar
Stan Hu committed
142
143
      end

144
145
146
147
      it "does not include statistics by default" do
        get api('/projects', user)

        expect(response).to have_http_status(200)
148
        expect(response).to include_pagination_headers
149
150
        expect(json_response).to be_an Array
        expect(json_response.first).not_to include('statistics')
Stan Hu's avatar
Stan Hu committed
151
152
      end

153
154
155
156
      it "includes statistics if requested" do
        get api('/projects', user), statistics: true

        expect(response).to have_http_status(200)
157
        expect(response).to include_pagination_headers
158
159
        expect(json_response).to be_an Array
        expect(json_response.first).to include 'statistics'
Stan Hu's avatar
Stan Hu committed
160
161
      end

162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
      context 'when external issue tracker is enabled' do
        let!(:jira_service) { create(:jira_service, project: project) }

        it 'includes open_issues_count' do
          get api('/projects', user)

          expect(response.status).to eq 200
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.first.keys).to include('open_issues_count')
          expect(json_response.find { |hash| hash['id'] == project.id }.keys).to include('open_issues_count')
        end

        it 'does not include open_issues_count if issues are disabled' do
          project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)

          get api('/projects', user)

          expect(response.status).to eq 200
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
        end
      end

187
      context 'and with simple=true' do
tiagonbotelho's avatar
tiagonbotelho committed
188
        it 'returns a simplified version of all the projects' do
189
190
191
192
193
194
195
196
          expected_keys = %w(
            id description default_branch tag_list
            ssh_url_to_repo http_url_to_repo web_url
            name name_with_namespace
            path path_with_namespace
            star_count forks_count
            created_at last_activity_at
          )
197

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

200
          expect(response).to have_http_status(200)
201
          expect(response).to include_pagination_headers
202
          expect(json_response).to be_an Array
203
          expect(json_response.first.keys).to match_array expected_keys
204
205
206
        end
      end

207
      context 'and using search' do
208
209
210
211
212
213
        it_behaves_like 'projects response' do
          let(:filter) { { search: project.name } }
          let(:current_user) { user }
          let(:projects) { [project] }
        end
      end
214

215
      context 'and membership=true' do
216
        it_behaves_like 'projects response' do
217
          let(:filter) { { membership: true } }
218
219
          let(:current_user) { user }
          let(:projects) { [project, project2, project3] }
220
221
222
        end
      end

Josh Frye's avatar
Josh Frye committed
223
      context 'and using the visibility filter' do
224
        it 'filters based on private visibility param' do
Josh Frye's avatar
Josh Frye committed
225
          get api('/projects', user), { visibility: 'private' }
226

227
          expect(response).to have_http_status(200)
228
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
229
          expect(json_response).to be_an Array
230
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id, project2.id, project3.id)
Josh Frye's avatar
Josh Frye committed
231
232
        end

233
        it 'filters based on internal visibility param' do
234
235
          project2.update_attribute(:visibility_level, Gitlab::VisibilityLevel::INTERNAL)

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

238
          expect(response).to have_http_status(200)
239
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
240
          expect(json_response).to be_an Array
241
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project2.id)
Josh Frye's avatar
Josh Frye committed
242
243
        end

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

247
          expect(response).to have_http_status(200)
248
          expect(response).to include_pagination_headers
Josh Frye's avatar
Josh Frye committed
249
          expect(json_response).to be_an Array
250
          expect(json_response.map { |p| p['id'] }).to contain_exactly(public_project.id)
Josh Frye's avatar
Josh Frye committed
251
252
253
        end
      end

254
      context 'and using sorting' do
255
        it 'returns the correct order when sorted by id' do
256
          get api('/projects', user), { order_by: 'id', sort: 'desc' }
257

258
          expect(response).to have_http_status(200)
259
          expect(response).to include_pagination_headers
260
261
          expect(json_response).to be_an Array
          expect(json_response.first['id']).to eq(project3.id)
262
263
        end
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
264

265
266
267
      context 'and with owned=true' do
        it 'returns an array of projects the user owns' do
          get api('/projects', user4), owned: true
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
268

269
          expect(response).to have_http_status(200)
270
          expect(response).to include_pagination_headers
271
272
273
          expect(json_response).to be_an Array
          expect(json_response.first['name']).to eq(project4.name)
          expect(json_response.first['owner']['username']).to eq(user4.username)
274
        end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
275
276
      end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      project = Project.first

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

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

      project = Project.first

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

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

      project = Project.first

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

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

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

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

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

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

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

      # 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)
438
    end
439

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

481
482
    it 'sets a project as allowing outdated diff discussions to automatically resolve' do
      project = attributes_for(:project, { resolve_outdated_diff_discussions: false })
483
      post api('/projects', user), project
484
      expect(json_response['resolve_outdated_diff_discussions']).to be_falsey
485
486
    end

487
488
    it 'sets a project as allowing outdated diff discussions to automatically resolve if resolve_outdated_diff_discussions' do
      project = attributes_for(:project, { resolve_outdated_diff_discussions: true })
489
      post api('/projects', user), project
490
      expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
491
492
    end

493
    it 'sets a project as allowing merge even if build fails' do
494
      project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
495
      post api('/projects', user), project
496
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
497
498
    end

499
500
    it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do
      project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: true })
501
      post api('/projects', user), project
502
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
503
504
    end

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

508
      post api('/projects', user), project
509
510

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

513
514
515
    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)

516
      post api('/projects', user), project
517
518
519
520

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

521
522
523
524
525
526
527
528
    it 'sets a project as allowing merge only if all discussions are resolved' do
      project = attributes_for(:project, { only_allow_merge_if_all_discussions_are_resolved: true })

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

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

529
530
531
532
533
534
    it 'ignores import_url when it is nil' do
      project = attributes_for(:project, { import_url: nil })

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

      expect(response).to have_http_status(201)
535
536
    end

537
    context 'when a visibility level is restricted' do
538
      let(:project_param) { attributes_for(:project, visibility: 'public') }
539

540
      before do
541
        stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
542
543
      end

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

547
        expect(response).to have_http_status(400)
548
549
550
551
552
        expect(json_response['message']['visibility_level'].first).to(
          match('restricted by your GitLab administrator')
        )
      end

553
      it 'allows an admin to override restricted visibility settings' do
554
555
        post api('/projects', admin), project_param

556
        expect(json_response['visibility']).to eq('public')
557
558
      end
    end
559
560
  end

vanadium23's avatar
vanadium23 committed
561
  describe 'GET /users/:user_id/projects/' do
562
    let!(:public_project) { create(:project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }
vanadium23's avatar
vanadium23 committed
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580

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

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

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

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

581
  describe 'POST /projects/user/:id' do
582
583
584
    before do
      expect(project).to be_persisted
    end
Angus MacArthur's avatar
Angus MacArthur committed
585

586
587
    it 'creates new project without path but with name and return 201' do
      expect { post api("/projects/user/#{user.id}", admin), name: 'Foo Project' }.to change {Project.count}.by(1)
588
      expect(response).to have_http_status(201)
589
590
591
592
593
594
595
596

      project = Project.first

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

    it 'creates new project with name and path and returns 201' do
597
598
      expect { post api("/projects/user/#{user.id}", admin), path: 'path-project-Foo', name: 'Foo Project' }
        .to change { Project.count }.by(1)
599
600
601
602
603
604
      expect(response).to have_http_status(201)

      project = Project.first

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

607
    it 'responds with 400 on failure and not project' do
608
609
      expect { post api("/projects/user/#{user.id}", admin) }
        .not_to change { Project.count }
610

611
      expect(response).to have_http_status(400)
Robert Schilling's avatar
Robert Schilling committed
612
      expect(json_response['error']).to eq('name is missing')
Angus MacArthur's avatar
Angus MacArthur committed
613
614
    end

615
    it 'assigns attributes to project' do
Angus MacArthur's avatar
Angus MacArthur committed
616
      project = attributes_for(:project, {
617
618
        issues_enabled: false,
        merge_requests_enabled: false,
619
620
        wiki_enabled: false,
        request_access_enabled: true
Angus MacArthur's avatar
Angus MacArthur committed
621
622
623
624
      })

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

Robert Schilling's avatar
Robert Schilling committed
625
      expect(response).to have_http_status(201)
626
      project.each_pair do |k, v|
627
        next if %i[has_external_issue_tracker path].include?(k)
628
        expect(json_response[k.to_s]).to eq(v)
Angus MacArthur's avatar
Angus MacArthur committed
629
630
      end
    end
631

632
    it 'sets a project as public' do
633
      project = attributes_for(:project, visibility: 'public')
634

635
      post api("/projects/user/#{user.id}", admin), project
Robert Schilling's avatar
Robert Schilling committed
636
637

      expect(response).to have_http_status(201)
638
      expect(json_response['visibility']).to eq('public')
639
    end
640

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

644
      post api("/projects/user/#{user.id}", admin), project
Robert Schilling's avatar
Robert Schilling committed
645
646

      expect(response).to have_http_status(201)
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')

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

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

658
659
    it 'sets a project as allowing outdated diff discussions to automatically resolve' do
      project = attributes_for(:project, { resolve_outdated_diff_discussions: false })
660
661
662

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

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

666
667
    it 'sets a project as allowing outdated diff discussions to automatically resolve only if resolve_outdated_diff_discussions' do
      project = attributes_for(:project, { resolve_outdated_diff_discussions: true })
668
669
670

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

671
      expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
672
673
    end

674
    it 'sets a project as allowing merge even if build fails' do
675
      project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
676
      post api("/projects/user/#{user.id}", admin), project
677
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
678
679
    end

680
681
    it 'sets a project as allowing merge only if merge_when_pipeline_succeeds' do
      project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: true })
682
      post api("/projects/user/#{user.id}", admin), project
683
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
684
    end
685

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

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

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

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

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

      expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
700
    end
Angus MacArthur's avatar
Angus MacArthur committed
701
702
  end

Douwe Maan's avatar
Douwe Maan committed
703
  describe "POST /projects/:id/uploads" do
704
705
706
    before do
      project
    end
Douwe Maan's avatar
Douwe Maan committed
707
708
709
710

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

711
      expect(response).to have_http_status(201)
Douwe Maan's avatar
Douwe Maan committed
712
713
714
715
716
717
      expect(json_response['alt']).to eq("dk")
      expect(json_response['url']).to start_with("/uploads/")
      expect(json_response['url']).to end_with("/dk.png")
    end
  end

718
  describe 'GET /projects/:id' do
719
720
    context 'when unauthenticated' do
      it 'returns the public projects' do
721
        public_project = create(:project, :public)
722

723
        get api("/projects/#{public_project.id}")
724

725
726
727
        expect(response).to have_http_status(200)
        expect(json_response['id']).to eq(public_project.id)
        expect(json_response['description']).to eq(public_project.description)
728
        expect(json_response['default_branch']).to eq(public_project.default_branch)
729
730
        expect(json_response.keys).not_to include('permissions')
      end
731
    end
732

733
734
735
736
737
    context 'when authenticated' do
      before do
        project
        project_member
      end
738

739
740
741
      it 'returns a project by id' do
        group = create(:group)
        link = create(:project_group_link, project: project, group: group)
742

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

745
746
747
748
749
750
        expect(response).to have_http_status(200)
        expect(json_response['id']).to eq(project.id)
        expect(json_response['description']).to eq(project.description)
        expect(json_response['default_branch']).to eq(project.default_branch)
        expect(json_response['tag_list']).to be_an Array
        expect(json_response['archived']).to be_falsey
751
        expect(json_response['visibility']).to be_present
752
753
754
755
756
757
758
759
760
761
        expect(json_response['ssh_url_to_repo']).to be_present
        expect(json_response['http_url_to_repo']).to be_present
        expect(json_response['web_url']).to be_present
        expect(json_response['owner']).to be_a Hash
        expect(json_response['owner']).to be_a Hash
        expect(json_response['name']).to eq(project.name)
        expect(json_response['path']).to be_present
        expect(json_response['issues_enabled']).to be_present
        expect(json_response['merge_requests_enabled']).to be_present
        expect(json_response['wiki_enabled']).to be_present
Toon Claes's avatar
Toon Claes committed
762
        expect(json_response['jobs_enabled']).to be_present
763
        expect(json_response['snippets_enabled']).to be_present
764
        expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions)
765
766
767
768
769
770
        expect(json_response['container_registry_enabled']).to be_present
        expect(json_response['created_at']).to be_present
        expect(json_response['last_activity_at']).to be_present
        expect(json_response['shared_runners_enabled']).to be_present
        expect(json_response['creator_id']).to be_present
        expect(json_response['namespace']).to be_present
771
772
        expect(json_response['import_status']).to be_present
        expect(json_response).to include("import_error")
773
774
775
        expect(json_response['avatar_url']).to be_nil
        expect(json_response['star_count']).to be_present
        expect(json_response['forks_count']).to be_present
Toon Claes's avatar
Toon Claes committed
776
        expect(json_response['public_jobs']).to be_present
777
        expect(json_response['ci_config_path']).to be_nil
778
779
780
781
782
        expect(json_response['shared_with_groups']).to be_an Array
        expect(json_response['shared_with_groups'].length).to eq(1)
        expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
        expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
        expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
783
        expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
784
785
        expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
      end
786

787
788
789
790
791
      it 'returns a project by path name' do
        get api("/projects/#{project.id}", user)
        expect(response).to have_http_status(200)
        expect(json_response['name']).to eq(project.name)
      end
792

793
794
795
796
797
      it 'returns a 404 error if not found' do
        get api('/projects/42', user)
        expect(response).to have_http_status(404)
        expect(json_response['message']).to eq('404 Project Not Found')
      end
798

799
800
801
802
      it 'returns a 404 error if user is not a member' do
        other_user = create(:user)
        get api("/projects/#{project.id}", other_user)
        expect(response).to have_http_status(404)
803
      end
804

805
806
      it 'handles users with dots' do
        dot_user = create(:user, username: 'dot.user')
807
        project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace)
808

809
        get api("/projects/#{CGI.escape(project.full_path)}", dot_user)
810
811
        expect(response).to have_http_status(200)
        expect(json_response['name']).to eq(project.name)
812
813
      end

814
815
      it 'exposes namespace fields' do
        get api("/projects/#{project.id}", user)
816

817
818
819
820
821
822
        expect(response).to have_http_status(200)
        expect(json_response['namespace']).to eq({
          'id' => user.namespace.id,
          'name' => user.namespace.name,
          'path' => user.namespace.path,
          'kind' => user.namespace.kind,
823
824
          'full_path' => user.namespace.full_path,
          'parent_id' => nil
825
        })
826
827
      end

828
829
      it "does not include statistics by default" do
        get api("/projects/#{project.id}", user)
830

831
832
833
        expect(response).to have_http_status(200)
        expect(json_response).not_to include 'statistics'
      end
834

835
836
      it "includes statistics if requested" do
        get api("/projects/#{project.id}", user), statistics: true
837

838
839
        expect(response).to have_http_status(200)
        expect(json_response).to include 'statistics'
840
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
841

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

845
846
        expect(response).to have_http_status(200)
        expect(json_response).to include("import_error")
847
848
      end

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