projects_spec.rb 58.1 KB
Newer Older
1
# -*- coding: utf-8 -*-
N
Nihad Abbasov 已提交
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) }
A
Angus MacArthur 已提交
10
  let(:admin) { create(:admin) }
11 12
  let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) }
  let(:project2) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
13
  let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
14
  let(:project_member) { create(:project_member, :developer, user: user3, project: project) }
15
  let(:user4) { create(:user) }
16 17
  let(:project3) do
    create(:project,
18
    :private,
19
    :repository,
20 21 22 23 24 25
    name: 'second_project',
    path: 'second_project',
    creator_id: user.id,
    namespace: user.namespace,
    merge_requests_enabled: false,
    issues_enabled: false, wiki_enabled: false,
W
winniehell 已提交
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(:empty_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
T
Toon Claes 已提交
44 45
    shared_examples_for 'projects response' do
      it 'returns an array of projects' do
46
        get api('/projects', current_user), filter
T
Toon Claes 已提交
47 48

        expect(response).to have_http_status(200)
49
        expect(response).to include_pagination_headers
T
Toon Claes 已提交
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 64 65 66 67 68 69 70 71 72
    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
          create(:empty_project, :public)
        end

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

T
Toon Claes 已提交
73 74 75 76 77 78 79
    let!(:public_project) { create(:empty_project, :public, name: 'public_project') }
    before do
      project
      project2
      project3
      project4
    end
D
Dmitriy Zaporozhets 已提交
80

81
    context 'when unauthenticated' do
T
Toon Claes 已提交
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
T
Toon Claes 已提交
89
        let(:current_user) { nil }
90
      end
N
Nihad Abbasov 已提交
91 92
    end

M
Markus Koller 已提交
93
    context 'when authenticated as regular user' do
T
Toon Claes 已提交
94
      it_behaves_like 'projects response' do
95
        let(:filter) { {} }
T
Toon Claes 已提交
96 97
        let(:current_user) { user }
        let(:projects) { [public_project, project, project2, project3] }
N
Nihad Abbasov 已提交
98
      end
99

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
      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
          create(:empty_project, :public, group: create(:group))
        end

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

115
      it 'includes the project labels as the tag_list' do
116
        get api('/projects', user)
T
Toon Claes 已提交
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
S
Stan Hu 已提交
125
        get api('/projects', user)
T
Toon Claes 已提交
126

S
Stan Hu 已提交
127
        expect(response.status).to eq 200
128
        expect(response).to include_pagination_headers
S
Stan Hu 已提交
129 130 131 132
        expect(json_response).to be_an Array
        expect(json_response.first.keys).to include('open_issues_count')
      end

T
Toon Claes 已提交
133
      it 'does not include open_issues_count if issues are disabled' do
F
Felipe Artur 已提交
134
        project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
S
Stan Hu 已提交
135 136

        get api('/projects', user)
T
Toon Claes 已提交
137

S
Stan Hu 已提交
138
        expect(response.status).to eq 200
139
        expect(response).to include_pagination_headers
S
Stan Hu 已提交
140
        expect(json_response).to be_an Array
T
Toon Claes 已提交
141
        expect(json_response.find { |hash| hash['id'] == project.id }.keys).not_to include('open_issues_count')
S
Stan Hu 已提交
142 143
      end

T
Toon Claes 已提交
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
T
Toon Claes 已提交
149 150
        expect(json_response).to be_an Array
        expect(json_response.first).not_to include('statistics')
S
Stan Hu 已提交
151 152
      end

T
Toon Claes 已提交
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
T
Toon Claes 已提交
158 159
        expect(json_response).to be_an Array
        expect(json_response.first).to include 'statistics'
S
Stan Hu 已提交
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

T
Toon Claes 已提交
187
      context 'and with simple=true' do
T
tiagonbotelho 已提交
188
        it 'returns a simplified version of all the projects' do
D
Douwe Maan 已提交
189
          expected_keys = %w(id http_url_to_repo web_url name name_with_namespace path path_with_namespace)
190

191
          get api('/projects?simple=true', user)
T
tiagonbotelho 已提交
192

193
          expect(response).to have_http_status(200)
194
          expect(response).to include_pagination_headers
195
          expect(json_response).to be_an Array
196
          expect(json_response.first.keys).to match_array expected_keys
197 198 199
        end
      end

200
      context 'and using search' do
201 202 203 204 205 206
        it_behaves_like 'projects response' do
          let(:filter) { { search: project.name } }
          let(:current_user) { user }
          let(:projects) { [project] }
        end
      end
T
Toon Claes 已提交
207

208
      context 'and membership=true' do
209
        it_behaves_like 'projects response' do
210
          let(:filter) { { membership: true } }
211 212
          let(:current_user) { user }
          let(:projects) { [project, project2, project3] }
213 214 215
        end
      end

J
Josh Frye 已提交
216
      context 'and using the visibility filter' do
217
        it 'filters based on private visibility param' do
J
Josh Frye 已提交
218
          get api('/projects', user), { visibility: 'private' }
T
Toon Claes 已提交
219

Z
Z.J. van de Weg 已提交
220
          expect(response).to have_http_status(200)
221
          expect(response).to include_pagination_headers
J
Josh Frye 已提交
222
          expect(json_response).to be_an Array
T
Toon Claes 已提交
223
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project.id, project2.id, project3.id)
J
Josh Frye 已提交
224 225
        end

226
        it 'filters based on internal visibility param' do
T
Toon Claes 已提交
227 228
          project2.update_attribute(:visibility_level, Gitlab::VisibilityLevel::INTERNAL)

J
Josh Frye 已提交
229
          get api('/projects', user), { visibility: 'internal' }
T
Toon Claes 已提交
230

Z
Z.J. van de Weg 已提交
231
          expect(response).to have_http_status(200)
232
          expect(response).to include_pagination_headers
J
Josh Frye 已提交
233
          expect(json_response).to be_an Array
T
Toon Claes 已提交
234
          expect(json_response.map { |p| p['id'] }).to contain_exactly(project2.id)
J
Josh Frye 已提交
235 236
        end

237
        it 'filters based on public visibility param' do
J
Josh Frye 已提交
238
          get api('/projects', user), { visibility: 'public' }
T
Toon Claes 已提交
239

Z
Z.J. van de Weg 已提交
240
          expect(response).to have_http_status(200)
241
          expect(response).to include_pagination_headers
J
Josh Frye 已提交
242
          expect(json_response).to be_an Array
T
Toon Claes 已提交
243
          expect(json_response.map { |p| p['id'] }).to contain_exactly(public_project.id)
J
Josh Frye 已提交
244 245 246
        end
      end

247
      context 'and using sorting' do
248
        it 'returns the correct order when sorted by id' do
249
          get api('/projects', user), { order_by: 'id', sort: 'desc' }
T
Toon Claes 已提交
250

Z
Z.J. van de Weg 已提交
251
          expect(response).to have_http_status(200)
252
          expect(response).to include_pagination_headers
253 254
          expect(json_response).to be_an Array
          expect(json_response.first['id']).to eq(project3.id)
255 256
        end
      end
N
Nihad Abbasov 已提交
257

T
Toon Claes 已提交
258 259 260
      context 'and with owned=true' do
        it 'returns an array of projects the user owns' do
          get api('/projects', user4), owned: true
D
Dmitriy Zaporozhets 已提交
261

T
Toon Claes 已提交
262
          expect(response).to have_http_status(200)
263
          expect(response).to include_pagination_headers
T
Toon Claes 已提交
264 265 266
          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)
267
        end
D
Dmitriy Zaporozhets 已提交
268 269
      end

T
Toon Claes 已提交
270 271
      context 'and with starred=true' do
        let(:public_project) { create(:empty_project, :public) }
D
Dmitriy Zaporozhets 已提交
272

T
Toon Claes 已提交
273
        before do
274
          project_member
T
Toon Claes 已提交
275 276
          user3.update_attributes(starred_projects: [project, project2, project3, public_project])
        end
M
Marin Jankovski 已提交
277

T
Toon Claes 已提交
278 279
        it 'returns the starred projects viewable by the user' do
          get api('/projects', user3), starred: true
M
Markus Koller 已提交
280

T
Toon Claes 已提交
281
          expect(response).to have_http_status(200)
282
          expect(response).to include_pagination_headers
T
Toon Claes 已提交
283 284
          expect(json_response).to be_an Array
          expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
285
        end
D
Dmitriy Zaporozhets 已提交
286 287
      end

T
Toon Claes 已提交
288
      context 'and with all query parameters' do
289
        let!(:project5) { create(:empty_project, :public, path: 'gitlab5', namespace: create(:namespace)) }
T
Toon Claes 已提交
290 291 292 293
        let!(:project6) { create(:empty_project, :public, path: 'project6', namespace: user.namespace) }
        let!(:project7) { create(:empty_project, :public, path: 'gitlab7', namespace: user.namespace) }
        let!(:project8) { create(:empty_project, path: 'gitlab8', namespace: user.namespace) }
        let!(:project9) { create(:empty_project, :public, path: 'gitlab9') }
294

T
Toon Claes 已提交
295
        before do
296
          user.update_attributes(starred_projects: [project5, project7, project8, project9])
T
Toon Claes 已提交
297
        end
298

299
        context 'including owned filter' do
300
          it 'returns only projects that satisfy all query parameters' do
301
            get api('/projects', user), { visibility: 'public', owned: true, starred: true, search: 'gitlab' }
302

303 304 305 306 307 308 309
            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
310

311
        context 'including membership filter' do
312 313 314 315 316 317
          before do
            create(:project_member,
                   user: user,
                   project: project5,
                   access_level: ProjectMember::MASTER)
          end
318

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

322 323 324 325
            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)
326
            expect(json_response.map { |project| project['id'] }).to contain_exactly(project5.id, project7.id)
327
          end
T
Toon Claes 已提交
328
        end
329 330
      end
    end
331

332
    context 'when authenticated as a different user' do
T
Toon Claes 已提交
333
      it_behaves_like 'projects response' do
334
        let(:filter) { {} }
335 336 337
        let(:current_user) { user2 }
        let(:projects) { [public_project] }
      end
338 339
    end

T
Toon Claes 已提交
340 341
    context 'when authenticated as admin' do
      it_behaves_like 'projects response' do
342
        let(:filter) { {} }
T
Toon Claes 已提交
343 344 345
        let(:current_user) { admin }
        let(:projects) { Project.all }
      end
346 347 348
    end
  end

349 350
  describe 'POST /projects' do
    context 'maximum number of projects reached' do
351
      it 'does not create new project and respond with 403' do
352
        allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0)
353 354
        expect { post api('/projects', user2), name: 'foo' }
          .to change {Project.count}.by(0)
Z
Z.J. van de Weg 已提交
355
        expect(response).to have_http_status(403)
356 357 358
      end
    end

359
    it 'creates new project without path but with name and returns 201' do
360 361
      expect { post api('/projects', user), name: 'Foo Project' }
        .to change { Project.count }.by(1)
Z
Z.J. van de Weg 已提交
362
      expect(response).to have_http_status(201)
363 364 365 366 367 368 369 370

      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
371 372
      expect { post api('/projects', user), path: 'foo_project' }
        .to change { Project.count }.by(1)
Z
Z.J. van de Weg 已提交
373
      expect(response).to have_http_status(201)
374 375 376 377 378 379 380

      project = Project.first

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

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

      project = Project.first

      expect(project.name).to eq('Foo Project')
389
      expect(project.path).to eq('path-project-Foo')
390 391
    end

392
    it 'creates last project before reaching project limit' do
393
      allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1)
394
      post api('/projects', user2), name: 'foo'
Z
Z.J. van de Weg 已提交
395
      expect(response).to have_http_status(201)
396 397
    end

398
    it 'does not create new project without name or path and returns 400' do
399
      expect { post api('/projects', user) }.not_to change { Project.count }
Z
Z.J. van de Weg 已提交
400
      expect(response).to have_http_status(400)
401
    end
A
Alex Denisov 已提交
402

403
    it "assigns attributes to project" do
404
      project = attributes_for(:project, {
405
        path: 'camelCasePath',
406
        issues_enabled: false,
W
winniehell 已提交
407
        jobs_enabled: false,
408
        merge_requests_enabled: false,
409
        wiki_enabled: false,
J
James Lopez 已提交
410
        only_allow_merge_if_pipeline_succeeds: false,
411
        request_access_enabled: true,
412
        only_allow_merge_if_all_discussions_are_resolved: false,
413
        ci_config_path: 'a/custom/path'
A
Alex Denisov 已提交
414 415
      })

416
      post api('/projects', user), project
A
Alex Denisov 已提交
417

W
winniehell 已提交
418 419
      expect(response).to have_http_status(201)

420
      project.each_pair do |k, v|
421
        next if %i[has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled].include?(k)
422
        expect(json_response[k.to_s]).to eq(v)
A
Alex Denisov 已提交
423
      end
F
Felipe Artur 已提交
424 425 426 427 428 429

      # 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)
430
    end
431

432
    it 'sets a project as public' do
433
      project = attributes_for(:project, visibility: 'public')
434

435
      post api('/projects', user), project
436 437

      expect(json_response['visibility']).to eq('public')
438 439
    end

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

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

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

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

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

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

V
vanadium23 已提交
456 457 458
    it 'sets tag list to a project' do
      project = attributes_for(:project, tag_list: %w[tagFirst tagSecond])

459
      post api('/projects', user), project
V
vanadium23 已提交
460 461

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

464 465 466
    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'))

467
      post api('/projects', user), project
468 469

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

473
    it 'sets a project as allowing merge even if build fails' do
J
James Lopez 已提交
474
      project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
475
      post api('/projects', user), project
J
James Lopez 已提交
476
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
477 478
    end

J
James Lopez 已提交
479 480
    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 })
481
      post api('/projects', user), project
J
James Lopez 已提交
482
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
483 484
    end

485 486 487
    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 })

488
      post api('/projects', user), project
489 490

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

493 494 495
    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)

496
      post api('/projects', user), project
497 498 499 500

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

501 502 503 504 505 506 507 508
    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

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

517
    context 'when a visibility level is restricted' do
518
      let(:project_param) { attributes_for(:project, visibility: 'public') }
519

520
      before do
521
        stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
522 523
      end

524
      it 'does not allow a non-admin to use a restricted visibility level' do
525
        post api('/projects', user), project_param
F
Felipe Artur 已提交
526

Z
Z.J. van de Weg 已提交
527
        expect(response).to have_http_status(400)
528 529 530 531 532
        expect(json_response['message']['visibility_level'].first).to(
          match('restricted by your GitLab administrator')
        )
      end

533
      it 'allows an admin to override restricted visibility settings' do
534 535
        post api('/projects', admin), project_param

536
        expect(json_response['visibility']).to eq('public')
537 538
      end
    end
539 540
  end

V
vanadium23 已提交
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
  describe 'GET /users/:user_id/projects/' do
    let!(:public_project) { create(:empty_project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }

    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

561
  describe 'POST /projects/user/:id' do
562 563 564
    before do
      expect(project).to be_persisted
    end
A
Angus MacArthur 已提交
565

566 567
    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)
Z
Z.J. van de Weg 已提交
568
      expect(response).to have_http_status(201)
569 570 571 572 573 574 575 576

      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
577 578
      expect { post api("/projects/user/#{user.id}", admin), path: 'path-project-Foo', name: 'Foo Project' }
        .to change { Project.count }.by(1)
579 580 581 582 583 584
      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')
A
Angus MacArthur 已提交
585 586
    end

587
    it 'responds with 400 on failure and not project' do
588 589
      expect { post api("/projects/user/#{user.id}", admin) }
        .not_to change { Project.count }
590

Z
Z.J. van de Weg 已提交
591
      expect(response).to have_http_status(400)
R
Robert Schilling 已提交
592
      expect(json_response['error']).to eq('name is missing')
A
Angus MacArthur 已提交
593 594
    end

595
    it 'assigns attributes to project' do
A
Angus MacArthur 已提交
596
      project = attributes_for(:project, {
597 598
        issues_enabled: false,
        merge_requests_enabled: false,
599 600
        wiki_enabled: false,
        request_access_enabled: true
A
Angus MacArthur 已提交
601 602 603 604
      })

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

R
Robert Schilling 已提交
605
      expect(response).to have_http_status(201)
606
      project.each_pair do |k, v|
607
        next if %i[has_external_issue_tracker path].include?(k)
608
        expect(json_response[k.to_s]).to eq(v)
A
Angus MacArthur 已提交
609 610
      end
    end
611

612
    it 'sets a project as public' do
613
      project = attributes_for(:project, visibility: 'public')
614

615
      post api("/projects/user/#{user.id}", admin), project
R
Robert Schilling 已提交
616 617

      expect(response).to have_http_status(201)
618
      expect(json_response['visibility']).to eq('public')
619
    end
620

621
    it 'sets a project as internal' do
622 623
      project = attributes_for(:project, visibility: 'internal')

624
      post api("/projects/user/#{user.id}", admin), project
R
Robert Schilling 已提交
625 626

      expect(response).to have_http_status(201)
627
      expect(json_response['visibility']).to eq('internal')
628 629
    end

630
    it 'sets a project as private' do
631 632
      project = attributes_for(:project, visibility: 'private')

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

      expect(json_response['visibility']).to eq('private')
636
    end
637

638
    it 'sets a project as allowing merge even if build fails' do
J
James Lopez 已提交
639
      project = attributes_for(:project, { only_allow_merge_if_pipeline_succeeds: false })
640
      post api("/projects/user/#{user.id}", admin), project
J
James Lopez 已提交
641
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
642 643
    end

J
James Lopez 已提交
644 645
    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 })
646
      post api("/projects/user/#{user.id}", admin), project
J
James Lopez 已提交
647
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
648
    end
649

650 651 652
    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 })

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

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

658 659 660
    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 })

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

      expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to be_truthy
664
    end
A
Angus MacArthur 已提交
665 666
  end

D
Douwe Maan 已提交
667
  describe "POST /projects/:id/uploads" do
668 669 670
    before do
      project
    end
D
Douwe Maan 已提交
671 672 673 674

    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")

Z
Z.J. van de Weg 已提交
675
      expect(response).to have_http_status(201)
D
Douwe Maan 已提交
676 677 678 679 680 681
      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

682
  describe 'GET /projects/:id' do
683 684
    context 'when unauthenticated' do
      it 'returns the public projects' do
685
        public_project = create(:empty_project, :public)
686

687
        get api("/projects/#{public_project.id}")
688

689 690 691 692 693
        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)
        expect(json_response.keys).not_to include('permissions')
      end
694
    end
N
Nihad Abbasov 已提交
695

696 697 698 699 700
    context 'when authenticated' do
      before do
        project
        project_member
      end
701

702 703 704
      it 'returns a project by id' do
        group = create(:group)
        link = create(:project_group_link, project: project, group: group)
705

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

708 709 710 711 712 713
        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
714
        expect(json_response['visibility']).to be_present
715 716 717 718 719 720 721 722 723 724
        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
T
Toon Claes 已提交
725
        expect(json_response['jobs_enabled']).to be_present
726 727 728 729 730 731 732
        expect(json_response['snippets_enabled']).to be_present
        expect(json_response['container_registry_enabled']).to be_present
        expect(json_response['created_at']).to be_present
        expect(json_response['last_activity_at']).to be_present
        expect(json_response['shared_runners_enabled']).to be_present
        expect(json_response['creator_id']).to be_present
        expect(json_response['namespace']).to be_present
733 734
        expect(json_response['import_status']).to be_present
        expect(json_response).to include("import_error")
735 736 737
        expect(json_response['avatar_url']).to be_nil
        expect(json_response['star_count']).to be_present
        expect(json_response['forks_count']).to be_present
T
Toon Claes 已提交
738
        expect(json_response['public_jobs']).to be_present
739
        expect(json_response['ci_config_path']).to be_nil
740 741 742 743 744
        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)
J
James Lopez 已提交
745
        expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
746 747
        expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved)
      end
748

749 750 751 752 753
      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
754

755 756 757 758 759
      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
760

761 762 763 764
      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)
765
      end
766

767 768
      it 'handles users with dots' do
        dot_user = create(:user, username: 'dot.user')
769
        project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace)
770

771 772 773
        get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user)
        expect(response).to have_http_status(200)
        expect(json_response['name']).to eq(project.name)
774 775
      end

776 777
      it 'exposes namespace fields' do
        get api("/projects/#{project.id}", user)
778

779 780 781 782 783 784
        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,
785 786
          'full_path' => user.namespace.full_path,
          'parent_id' => nil
787
        })
788 789
      end

790 791
      it "does not include statistics by default" do
        get api("/projects/#{project.id}", user)
792

793 794 795
        expect(response).to have_http_status(200)
        expect(json_response).not_to include 'statistics'
      end
796

797 798
      it "includes statistics if requested" do
        get api("/projects/#{project.id}", user), statistics: true
799

800 801
        expect(response).to have_http_status(200)
        expect(json_response).to include 'statistics'
802
      end
N
Nihad Abbasov 已提交
803

804 805
      it "includes import_error if user can admin project" do
        get api("/projects/#{project.id}", user)
D
Dmitriy Zaporozhets 已提交
806

807 808
        expect(response).to have_http_status(200)
        expect(json_response).to include("import_error")
D
Dmitriy Zaporozhets 已提交
809 810
      end

811 812
      it "does not include import_error if user cannot admin project" do
        get api("/projects/#{project.id}", user3)
D
Dmitriy Zaporozhets 已提交
813

814 815 816
        expect(response).to have_http_status(200)
        expect(json_response).not_to include("import_error")
      end
D
Dmitriy Zaporozhets 已提交
817

818 819
      describe 'permissions' do
        context 'all projects' do
820 821 822
          before do
            project.team << [user, :master]
          end
823 824 825 826 827

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

            expect(response).to have_http_status(200)
828 829
            expect(json_response.first['permissions']['project_access']['access_level'])
            .to eq(Gitlab::Access::MASTER)
830 831 832 833 834 835 836 837 838 839
            expect(json_response.first['permissions']['group_access']).to be_nil
          end
        end

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

            expect(response).to have_http_status(200)
840 841
            expect(json_response['permissions']['project_access']['access_level'])
            .to eq(Gitlab::Access::MASTER)
842 843
            expect(json_response['permissions']['group_access']).to be_nil
          end
844
        end
845

846
        context 'group project' do
847
          let(:project2) { create(:empty_project, group: create(:group)) }
848

849 850 851
          before do
            project2.group.add_owner(user)
          end
852

853 854
          it 'sets the owner and return 200' do
            get api("/projects/#{project2.id}", user)
855

856 857
            expect(response).to have_http_status(200)
            expect(json_response['permissions']['project_access']).to be_nil
858 859
            expect(json_response['permissions']['group_access']['access_level'])
            .to eq(Gitlab::Access::OWNER)
860
          end
861
        end
D
Dmitriy Zaporozhets 已提交
862
      end
863
    end
N
Nihad Abbasov 已提交
864
  end
D
Dmitriy Zaporozhets 已提交
865

866 867 868 869
  describe 'GET /projects/:id/users' do
    shared_examples_for 'project users response' do
      it 'returns the project users' do
        get api("/projects/#{project.id}/users", current_user)
D
Dmitriy Zaporozhets 已提交
870

871 872
        user = project.namespace.owner

873
        expect(response).to have_http_status(200)
874
        expect(response).to include_pagination_headers
875 876 877 878
        expect(json_response).to be_an Array
        expect(json_response.size).to eq(1)

        first_user = json_response.first
879 880
        expect(first_user['username']).to eq(user.username)
        expect(first_user['name']).to eq(user.name)
881
        expect(first_user.keys).to contain_exactly(*%w[name username id state avatar_url web_url])
D
Dmitriy Zaporozhets 已提交
882
      end
D
Dmitriy Zaporozhets 已提交
883 884
    end

885 886
    context 'when unauthenticated' do
      it_behaves_like 'project users response' do
887
        let(:project) { create(:empty_project, :public) }
888 889
        let(:current_user) { nil }
      end
D
Dmitriy Zaporozhets 已提交
890 891
    end

892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912
    context 'when authenticated' do
      context 'valid request' do
        it_behaves_like 'project users response' do
          let(:current_user) { user }
        end
      end

      it 'returns a 404 error if not found' do
        get api('/projects/42/users', user)

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

      it 'returns a 404 error if user is not a member' do
        other_user = create(:user)

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

        expect(response).to have_http_status(404)
      end
D
Dmitriy Zaporozhets 已提交
913 914 915
    end
  end

916
  describe 'GET /projects/:id/snippets' do
917 918 919
    before do
      snippet
    end
D
Dmitriy Zaporozhets 已提交
920

921
    it 'returns an array of project snippets' do
922
      get api("/projects/#{project.id}/snippets", user)
923

Z
Z.J. van de Weg 已提交
924
      expect(response).to have_http_status(200)
925
      expect(response).to include_pagination_headers
926 927
      expect(json_response).to be_an Array
      expect(json_response.first['title']).to eq(snippet.title)
N
Nihad Abbasov 已提交
928 929 930
    end
  end

931
  describe 'GET /projects/:id/snippets/:snippet_id' do
932
    it 'returns a project snippet' do
933
      get api("/projects/#{project.id}/snippets/#{snippet.id}", user)
Z
Z.J. van de Weg 已提交
934
      expect(response).to have_http_status(200)
935
      expect(json_response['title']).to eq(snippet.title)
N
Nihad Abbasov 已提交
936
    end
937

938
    it 'returns a 404 error if snippet id not found' do
939
      get api("/projects/#{project.id}/snippets/1234", user)
Z
Z.J. van de Weg 已提交
940
      expect(response).to have_http_status(404)
941
    end
N
Nihad Abbasov 已提交
942 943
  end

944
  describe 'POST /projects/:id/snippets' do
945
    it 'creates a new project snippet' do
946
      post api("/projects/#{project.id}/snippets", user),
947
        title: 'api test', file_name: 'sample.rb', code: 'test', visibility: 'private'
Z
Z.J. van de Weg 已提交
948
      expect(response).to have_http_status(201)
949
      expect(json_response['title']).to eq('api test')
N
Nihad Abbasov 已提交
950
    end
951

952
    it 'returns a 400 error if invalid snippet is given' do
953 954
      post api("/projects/#{project.id}/snippets", user)
      expect(status).to eq(400)
955
    end
N
Nihad Abbasov 已提交
956 957
  end

958
  describe 'PUT /projects/:id/snippets/:snippet_id' do
959
    it 'updates an existing project snippet' do
960
      put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
961
        code: 'updated code'
Z
Z.J. van de Weg 已提交
962
      expect(response).to have_http_status(200)
963 964
      expect(json_response['title']).to eq('example')
      expect(snippet.reload.content).to eq('updated code')
965
    end
966

967
    it 'updates an existing project snippet with new title' do
968
      put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
969
        title: 'other api test'
Z
Z.J. van de Weg 已提交
970
      expect(response).to have_http_status(200)
971
      expect(json_response['title']).to eq('other api test')
972
    end
973 974
  end

975
  describe 'DELETE /projects/:id/snippets/:snippet_id' do
976 977 978
    before do
      snippet
    end
D
Dmitriy Zaporozhets 已提交
979

980
    it 'deletes existing project snippet' do
981
      expect do
982
        delete api("/projects/#{project.id}/snippets/#{snippet.id}", user)
983 984

        expect(response).to have_http_status(204)
985
      end.to change { Snippet.count }.by(-1)
986 987
    end

988
    it 'returns 404 when deleting unknown snippet id' do
989
      delete api("/projects/#{project.id}/snippets/1234", user)
Z
Z.J. van de Weg 已提交
990
      expect(response).to have_http_status(404)
N
Nihad Abbasov 已提交
991 992
    end
  end
993

994
  describe 'GET /projects/:id/snippets/:snippet_id/raw' do
995
    it 'gets a raw project snippet' do
996
      get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user)
Z
Z.J. van de Weg 已提交
997
      expect(response).to have_http_status(200)
998
    end
999

1000
    it 'returns a 404 error if raw project snippet not found' do
1001
      get api("/projects/#{project.id}/snippets/5555/raw", user)
Z
Z.J. van de Weg 已提交
1002
      expect(response).to have_http_status(404)
1003
    end
1004
  end
1005

1006
  describe 'fork management' do
1007 1008
    let(:project_fork_target) { create(:empty_project) }
    let(:project_fork_source) { create(:empty_project, :public) }
1009

1010
    describe 'POST /projects/:id/fork/:forked_from_id' do
1011
      let(:new_project_fork_source) { create(:empty_project, :public) }
1012

1013
      it "is not available for non admin users" do
1014
        post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
Z
Z.J. van de Weg 已提交
1015
        expect(response).to have_http_status(403)
1016 1017
      end

1018
      it 'allows project to be forked from an existing project' do
1019
        expect(project_fork_target.forked?).not_to be_truthy
1020
        post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
Z
Z.J. van de Weg 已提交
1021
        expect(response).to have_http_status(201)
1022
        project_fork_target.reload
1023 1024 1025
        expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
        expect(project_fork_target.forked_project_link).not_to be_nil
        expect(project_fork_target.forked?).to be_truthy
1026 1027
      end

1028
      it 'fails if forked_from project which does not exist' do
1029
        post api("/projects/#{project_fork_target.id}/fork/9999", admin)
Z
Z.J. van de Weg 已提交
1030
        expect(response).to have_http_status(404)
1031 1032
      end

1033
      it 'fails with 409 if already forked' do
1034 1035
        post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
        project_fork_target.reload
1036
        expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
1037
        post api("/projects/#{project_fork_target.id}/fork/#{new_project_fork_source.id}", admin)
Z
Z.J. van de Weg 已提交
1038
        expect(response).to have_http_status(409)
1039
        project_fork_target.reload
1040 1041
        expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id)
        expect(project_fork_target.forked?).to be_truthy
1042 1043 1044
      end
    end

1045
    describe 'DELETE /projects/:id/fork' do
1046
      it "is not visible to users outside group" do
1047
        delete api("/projects/#{project_fork_target.id}/fork", user)
Z
Z.J. van de Weg 已提交
1048
        expect(response).to have_http_status(404)
1049 1050
      end

1051
      context 'when users belong to project group' do
1052
        let(:project_fork_target) { create(:empty_project, group: create(:group)) }
1053

1054 1055 1056 1057 1058
        before do
          project_fork_target.group.add_owner user
          project_fork_target.group.add_developer user2
        end

1059
        it 'is forbidden to non-owner users' do
1060
          delete api("/projects/#{project_fork_target.id}/fork", user2)
Z
Z.J. van de Weg 已提交
1061
          expect(response).to have_http_status(403)
1062 1063
        end

1064
        it 'makes forked project unforked' do
1065 1066 1067 1068
          post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin)
          project_fork_target.reload
          expect(project_fork_target.forked_from_project).not_to be_nil
          expect(project_fork_target.forked?).to be_truthy
1069

1070
          delete api("/projects/#{project_fork_target.id}/fork", admin)
1071 1072

          expect(response).to have_http_status(204)
1073 1074 1075 1076 1077
          project_fork_target.reload
          expect(project_fork_target.forked_from_project).to be_nil
          expect(project_fork_target.forked?).not_to be_truthy
        end

1078
        it 'is idempotent if not forked' do
1079 1080
          expect(project_fork_target.forked_from_project).to be_nil
          delete api("/projects/#{project_fork_target.id}/fork", admin)
R
Robert Schilling 已提交
1081
          expect(response).to have_http_status(304)
1082 1083
          expect(project_fork_target.reload.forked_from_project).to be_nil
        end
1084 1085 1086
      end
    end
  end
1087

1088 1089 1090
  describe "POST /projects/:id/share" do
    let(:group) { create(:group) }

1091
    it "shares project with group" do
1092 1093
      expires_at = 10.days.from_now.to_date

1094
      expect do
1095
        post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER, expires_at: expires_at
1096 1097
      end.to change { ProjectGroupLink.count }.by(1)

R
Robert Schilling 已提交
1098
      expect(response).to have_http_status(201)
1099 1100 1101
      expect(json_response['group_id']).to eq(group.id)
      expect(json_response['group_access']).to eq(Gitlab::Access::DEVELOPER)
      expect(json_response['expires_at']).to eq(expires_at.to_s)
1102 1103
    end

1104
    it "returns a 400 error when group id is not given" do
1105
      post api("/projects/#{project.id}/share", user), group_access: Gitlab::Access::DEVELOPER
R
Robert Schilling 已提交
1106
      expect(response).to have_http_status(400)
1107 1108
    end

1109
    it "returns a 400 error when access level is not given" do
1110
      post api("/projects/#{project.id}/share", user), group_id: group.id
R
Robert Schilling 已提交
1111
      expect(response).to have_http_status(400)
1112 1113
    end

1114
    it "returns a 400 error when sharing is disabled" do
1115 1116
      project.namespace.update(share_with_group_lock: true)
      post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: Gitlab::Access::DEVELOPER
R
Robert Schilling 已提交
1117
      expect(response).to have_http_status(400)
1118 1119
    end

1120 1121 1122 1123 1124
    it 'returns a 404 error when user cannot read group' do
      private_group = create(:group, :private)

      post api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER

R
Robert Schilling 已提交
1125
      expect(response).to have_http_status(404)
1126 1127
    end

1128 1129 1130
    it 'returns a 404 error when group does not exist' do
      post api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER

R
Robert Schilling 已提交
1131
      expect(response).to have_http_status(404)
1132 1133
    end

R
Robert Schilling 已提交
1134
    it "returns a 400 error when wrong params passed" do
1135
      post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234
R
Robert Schilling 已提交
1136 1137 1138

      expect(response).to have_http_status(400)
      expect(json_response['error']).to eq 'group_access does not have a valid value'
1139 1140 1141
    end
  end

1142 1143 1144 1145
  describe 'DELETE /projects/:id/share/:group_id' do
    it 'returns 204 when deleting a group share' do
      group = create(:group, :public)
      create(:project_group_link, group: group, project: project)
1146

1147 1148 1149 1150
      delete api("/projects/#{project.id}/share/#{group.id}", user)

      expect(response).to have_http_status(204)
      expect(project.project_group_links).to be_empty
1151 1152
    end

1153 1154 1155 1156
    it 'returns a 400 when group id is not an integer' do
      delete api("/projects/#{project.id}/share/foo", user)

      expect(response).to have_http_status(400)
1157 1158
    end

1159 1160 1161 1162 1163 1164 1165 1166 1167 1168
    it 'returns a 404 error when group link does not exist' do
      delete api("/projects/#{project.id}/share/1234", user)

      expect(response).to have_http_status(404)
    end

    it 'returns a 404 error when project does not exist' do
      delete api("/projects/123/share/1234", user)

      expect(response).to have_http_status(404)
1169 1170
    end
  end
1171

1172
  describe 'PUT /projects/:id' do
1173 1174 1175 1176 1177 1178 1179 1180 1181 1182
    before do
      expect(project).to be_persisted
      expect(user).to be_persisted
      expect(user3).to be_persisted
      expect(user4).to be_persisted
      expect(project3).to be_persisted
      expect(project4).to be_persisted
      expect(project_member2).to be_persisted
      expect(project_member).to be_persisted
    end
1183

1184 1185
    it 'returns 400 when nothing sent' do
      project_param = {}
W
winniehell 已提交
1186

1187
      put api("/projects/#{project.id}", user), project_param
W
winniehell 已提交
1188

1189 1190 1191
      expect(response).to have_http_status(400)
      expect(json_response['error']).to match('at least one parameter must be provided')
    end
1192 1193

    context 'when unauthenticated' do
1194
      it 'returns authentication error' do
1195
        project_param = { name: 'bar' }
W
winniehell 已提交
1196

1197
        put api("/projects/#{project.id}"), project_param
W
winniehell 已提交
1198

Z
Z.J. van de Weg 已提交
1199
        expect(response).to have_http_status(401)
1200 1201 1202 1203
      end
    end

    context 'when authenticated as project owner' do
1204
      it 'updates name' do
1205
        project_param = { name: 'bar' }
W
winniehell 已提交
1206

1207
        put api("/projects/#{project.id}", user), project_param
W
winniehell 已提交
1208

Z
Z.J. van de Weg 已提交
1209
        expect(response).to have_http_status(200)
W
winniehell 已提交
1210

1211
        project_param.each_pair do |k, v|
1212
          expect(json_response[k.to_s]).to eq(v)
1213 1214 1215
        end
      end

1216
      it 'updates visibility_level' do
1217
        project_param = { visibility: 'public' }
W
winniehell 已提交
1218

1219
        put api("/projects/#{project3.id}", user), project_param
W
winniehell 已提交
1220

Z
Z.J. van de Weg 已提交
1221
        expect(response).to have_http_status(200)
W
winniehell 已提交
1222

1223
        project_param.each_pair do |k, v|
1224
          expect(json_response[k.to_s]).to eq(v)
1225 1226 1227
        end
      end

1228
      it 'updates visibility_level from public to private' do
1229
        project3.update_attributes({ visibility_level: Gitlab::VisibilityLevel::PUBLIC })
1230
        project_param = { visibility: 'private' }
1231 1232

        put api("/projects/#{project3.id}", user), project_param
W
winniehell 已提交
1233

Z
Z.J. van de Weg 已提交
1234
        expect(response).to have_http_status(200)
W
winniehell 已提交
1235

1236 1237 1238
        project_param.each_pair do |k, v|
          expect(json_response[k.to_s]).to eq(v)
        end
W
winniehell 已提交
1239

1240
        expect(json_response['visibility']).to eq('private')
1241 1242
      end

1243
      it 'does not update name to existing name' do
1244
        project_param = { name: project3.name }
W
winniehell 已提交
1245

1246
        put api("/projects/#{project.id}", user), project_param
W
winniehell 已提交
1247

Z
Z.J. van de Weg 已提交
1248
        expect(response).to have_http_status(400)
1249
        expect(json_response['message']['name']).to eq(['has already been taken'])
1250 1251
      end

1252 1253 1254 1255 1256 1257 1258 1259 1260
      it 'updates request_access_enabled' do
        project_param = { request_access_enabled: false }

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

        expect(response).to have_http_status(200)
        expect(json_response['request_access_enabled']).to eq(false)
      end

1261
      it 'updates path & name to existing path & name in different namespace' do
1262
        project_param = { path: project4.path, name: project4.name }
W
winniehell 已提交
1263

1264
        put api("/projects/#{project3.id}", user), project_param
W
winniehell 已提交
1265

Z
Z.J. van de Weg 已提交
1266
        expect(response).to have_http_status(200)
W
winniehell 已提交
1267

1268
        project_param.each_pair do |k, v|
1269
          expect(json_response[k.to_s]).to eq(v)
1270 1271
        end
      end
W
winniehell 已提交
1272 1273 1274

      it 'updates jobs_enabled' do
        project_param = { jobs_enabled: true }
W
winniehell 已提交
1275

1276
        put api("/projects/#{project3.id}", user), project_param
W
winniehell 已提交
1277

Z
Z.J. van de Weg 已提交
1278
        expect(response).to have_http_status(200)
W
winniehell 已提交
1279

1280
        project_param.each_pair do |k, v|
1281
          expect(json_response[k.to_s]).to eq(v)
1282 1283 1284 1285 1286
        end
      end
    end

    context 'when authenticated as project master' do
1287
      it 'updates path' do
1288 1289
        project_param = { path: 'bar' }
        put api("/projects/#{project3.id}", user4), project_param
Z
Z.J. van de Weg 已提交
1290
        expect(response).to have_http_status(200)
1291
        project_param.each_pair do |k, v|
1292
          expect(json_response[k.to_s]).to eq(v)
1293 1294 1295
        end
      end

1296
      it 'updates other attributes' do
1297 1298 1299 1300 1301 1302 1303
        project_param = { issues_enabled: true,
                          wiki_enabled: true,
                          snippets_enabled: true,
                          merge_requests_enabled: true,
                          description: 'new description' }

        put api("/projects/#{project3.id}", user4), project_param
Z
Z.J. van de Weg 已提交
1304
        expect(response).to have_http_status(200)
1305
        project_param.each_pair do |k, v|
1306
          expect(json_response[k.to_s]).to eq(v)
1307 1308 1309
        end
      end

1310
      it 'does not update path to existing path' do
1311 1312
        project_param = { path: project.path }
        put api("/projects/#{project3.id}", user4), project_param
Z
Z.J. van de Weg 已提交
1313
        expect(response).to have_http_status(400)
1314
        expect(json_response['message']['path']).to eq(['has already been taken'])
1315 1316
      end

1317
      it 'does not update name' do
1318 1319
        project_param = { name: 'bar' }
        put api("/projects/#{project3.id}", user4), project_param
Z
Z.J. van de Weg 已提交
1320
        expect(response).to have_http_status(403)
1321 1322
      end

1323
      it 'does not update visibility_level' do
1324
        project_param = { visibility: 'public' }
1325
        put api("/projects/#{project3.id}", user4), project_param
Z
Z.J. van de Weg 已提交
1326
        expect(response).to have_http_status(403)
1327 1328 1329 1330
      end
    end

    context 'when authenticated as project developer' do
1331
      it 'does not update other attributes' do
1332 1333 1334 1335 1336
        project_param = { path: 'bar',
                          issues_enabled: true,
                          wiki_enabled: true,
                          snippets_enabled: true,
                          merge_requests_enabled: true,
1337 1338
                          description: 'new description',
                          request_access_enabled: true }
1339
        put api("/projects/#{project.id}", user3), project_param
Z
Z.J. van de Weg 已提交
1340
        expect(response).to have_http_status(403)
1341 1342 1343 1344
      end
    end
  end

1345
  describe 'POST /projects/:id/archive' do
1346 1347
    context 'on an unarchived project' do
      it 'archives the project' do
1348
        post api("/projects/#{project.id}/archive", user)
1349

Z
Z.J. van de Weg 已提交
1350
        expect(response).to have_http_status(201)
1351 1352 1353 1354 1355 1356 1357 1358 1359 1360
        expect(json_response['archived']).to be_truthy
      end
    end

    context 'on an archived project' do
      before do
        project.archive!
      end

      it 'remains archived' do
1361
        post api("/projects/#{project.id}/archive", user)
1362

Z
Z.J. van de Weg 已提交
1363
        expect(response).to have_http_status(201)
1364 1365
        expect(json_response['archived']).to be_truthy
      end
1366
    end
1367

1368 1369 1370 1371
    context 'user without archiving rights to the project' do
      before do
        project.team << [user3, :developer]
      end
1372

1373 1374 1375
      it 'rejects the action' do
        post api("/projects/#{project.id}/archive", user3)

Z
Z.J. van de Weg 已提交
1376
        expect(response).to have_http_status(403)
1377 1378 1379 1380
      end
    end
  end

1381
  describe 'POST /projects/:id/unarchive' do
1382 1383
    context 'on an unarchived project' do
      it 'remains unarchived' do
1384
        post api("/projects/#{project.id}/unarchive", user)
1385

Z
Z.J. van de Weg 已提交
1386
        expect(response).to have_http_status(201)
1387 1388 1389 1390 1391 1392 1393 1394 1395
        expect(json_response['archived']).to be_falsey
      end
    end

    context 'on an archived project' do
      before do
        project.archive!
      end

1396 1397
      it 'unarchives the project' do
        post api("/projects/#{project.id}/unarchive", user)
1398

Z
Z.J. van de Weg 已提交
1399
        expect(response).to have_http_status(201)
1400 1401
        expect(json_response['archived']).to be_falsey
      end
1402
    end
1403

1404 1405 1406 1407
    context 'user without archiving rights to the project' do
      before do
        project.team << [user3, :developer]
      end
1408

1409 1410 1411
      it 'rejects the action' do
        post api("/projects/#{project.id}/unarchive", user3)

Z
Z.J. van de Weg 已提交
1412
        expect(response).to have_http_status(403)
1413 1414 1415 1416
      end
    end
  end

1417 1418 1419
  describe 'POST /projects/:id/star' do
    context 'on an unstarred project' do
      it 'stars the project' do
1420
        expect { post api("/projects/#{project.id}/star", user) }.to change { project.reload.star_count }.by(1)
1421

Z
Z.J. van de Weg 已提交
1422
        expect(response).to have_http_status(201)
1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433
        expect(json_response['star_count']).to eq(1)
      end
    end

    context 'on a starred project' do
      before do
        user.toggle_star(project)
        project.reload
      end

      it 'does not modify the star count' do
1434
        expect { post api("/projects/#{project.id}/star", user) }.not_to change { project.reload.star_count }
1435

Z
Z.J. van de Weg 已提交
1436
        expect(response).to have_http_status(304)
1437 1438 1439 1440
      end
    end
  end

1441
  describe 'POST /projects/:id/unstar' do
1442 1443 1444 1445 1446 1447 1448
    context 'on a starred project' do
      before do
        user.toggle_star(project)
        project.reload
      end

      it 'unstars the project' do
1449
        expect { post api("/projects/#{project.id}/unstar", user) }.to change { project.reload.star_count }.by(-1)
1450

1451
        expect(response).to have_http_status(201)
1452 1453 1454 1455 1456 1457
        expect(json_response['star_count']).to eq(0)
      end
    end

    context 'on an unstarred project' do
      it 'does not modify the star count' do
1458
        expect { post api("/projects/#{project.id}/unstar", user) }.not_to change { project.reload.star_count }
1459

Z
Z.J. van de Weg 已提交
1460
        expect(response).to have_http_status(304)
1461 1462 1463 1464
      end
    end
  end

1465 1466
  describe 'DELETE /projects/:id' do
    context 'when authenticated as user' do
1467
      it 'removes project' do
1468
        delete api("/projects/#{project.id}", user)
1469 1470 1471

        expect(response).to have_http_status(202)
        expect(json_response['message']).to eql('202 Accepted')
1472 1473
      end

1474
      it 'does not remove a project if not an owner' do
1475 1476 1477
        user3 = create(:user)
        project.team << [user3, :developer]
        delete api("/projects/#{project.id}", user3)
Z
Z.J. van de Weg 已提交
1478
        expect(response).to have_http_status(403)
1479 1480
      end

1481
      it 'does not remove a non existing project' do
1482
        delete api('/projects/1328', user)
Z
Z.J. van de Weg 已提交
1483
        expect(response).to have_http_status(404)
1484 1485
      end

1486
      it 'does not remove a project not attached to user' do
1487
        delete api("/projects/#{project.id}", user2)
Z
Z.J. van de Weg 已提交
1488
        expect(response).to have_http_status(404)
1489 1490 1491
      end
    end

1492
    context 'when authenticated as admin' do
1493
      it 'removes any existing project' do
1494
        delete api("/projects/#{project.id}", admin)
1495 1496 1497

        expect(response).to have_http_status(202)
        expect(json_response['message']).to eql('202 Accepted')
1498 1499
      end

1500
      it 'does not remove a non existing project' do
1501
        delete api('/projects/1328', admin)
Z
Z.J. van de Weg 已提交
1502
        expect(response).to have_http_status(404)
1503 1504 1505
      end
    end
  end
1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531

  describe 'POST /projects/:id/fork' do
    let(:project) do
      create(:project, :repository, creator: user, namespace: user.namespace)
    end
    let(:group) { create(:group) }
    let(:group2) do
      group = create(:group, name: 'group2_name')
      group.add_owner(user2)
      group
    end

    before do
      project.add_reporter(user2)
    end

    context 'when authenticated' do
      it 'forks if user has sufficient access to project' do
        post api("/projects/#{project.id}/fork", user2)

        expect(response).to have_http_status(201)
        expect(json_response['name']).to eq(project.name)
        expect(json_response['path']).to eq(project.path)
        expect(json_response['owner']['id']).to eq(user2.id)
        expect(json_response['namespace']['id']).to eq(user2.namespace.id)
        expect(json_response['forked_from_project']['id']).to eq(project.id)
R
Rémy Coutable 已提交
1532
        expect(json_response['import_status']).to eq('scheduled')
1533
        expect(json_response).to include("import_error")
1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544
      end

      it 'forks if user is admin' do
        post api("/projects/#{project.id}/fork", admin)

        expect(response).to have_http_status(201)
        expect(json_response['name']).to eq(project.name)
        expect(json_response['path']).to eq(project.path)
        expect(json_response['owner']['id']).to eq(admin.id)
        expect(json_response['namespace']['id']).to eq(admin.namespace.id)
        expect(json_response['forked_from_project']['id']).to eq(project.id)
R
Rémy Coutable 已提交
1545
        expect(json_response['import_status']).to eq('scheduled')
1546
        expect(json_response).to include("import_error")
1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635
      end

      it 'fails on missing project access for the project to fork' do
        new_user = create(:user)
        post api("/projects/#{project.id}/fork", new_user)

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

      it 'fails if forked project exists in the user namespace' do
        post api("/projects/#{project.id}/fork", user)

        expect(response).to have_http_status(409)
        expect(json_response['message']['name']).to eq(['has already been taken'])
        expect(json_response['message']['path']).to eq(['has already been taken'])
      end

      it 'fails if project to fork from does not exist' do
        post api('/projects/424242/fork', user)

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

      it 'forks with explicit own user namespace id' do
        post api("/projects/#{project.id}/fork", user2), namespace: user2.namespace.id

        expect(response).to have_http_status(201)
        expect(json_response['owner']['id']).to eq(user2.id)
      end

      it 'forks with explicit own user name as namespace' do
        post api("/projects/#{project.id}/fork", user2), namespace: user2.username

        expect(response).to have_http_status(201)
        expect(json_response['owner']['id']).to eq(user2.id)
      end

      it 'forks to another user when admin' do
        post api("/projects/#{project.id}/fork", admin), namespace: user2.username

        expect(response).to have_http_status(201)
        expect(json_response['owner']['id']).to eq(user2.id)
      end

      it 'fails if trying to fork to another user when not admin' do
        post api("/projects/#{project.id}/fork", user2), namespace: admin.namespace.id

        expect(response).to have_http_status(404)
      end

      it 'fails if trying to fork to non-existent namespace' do
        post api("/projects/#{project.id}/fork", user2), namespace: 42424242

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

      it 'forks to owned group' do
        post api("/projects/#{project.id}/fork", user2), namespace: group2.name

        expect(response).to have_http_status(201)
        expect(json_response['namespace']['name']).to eq(group2.name)
      end

      it 'fails to fork to not owned group' do
        post api("/projects/#{project.id}/fork", user2), namespace: group.name

        expect(response).to have_http_status(404)
      end

      it 'forks to not owned group when admin' do
        post api("/projects/#{project.id}/fork", admin), namespace: group.name

        expect(response).to have_http_status(201)
        expect(json_response['namespace']['name']).to eq(group.name)
      end
    end

    context 'when unauthenticated' do
      it 'returns authentication error' do
        post api("/projects/#{project.id}/fork")

        expect(response).to have_http_status(401)
        expect(json_response['message']).to eq('401 Unauthorized')
      end
    end
  end
1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666

  describe 'POST /projects/:id/housekeeping' do
    let(:housekeeping) { Projects::HousekeepingService.new(project) }

    before do
      allow(Projects::HousekeepingService).to receive(:new).with(project).and_return(housekeeping)
    end

    context 'when authenticated as owner' do
      it 'starts the housekeeping process' do
        expect(housekeeping).to receive(:execute).once

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

        expect(response).to have_http_status(201)
      end

      context 'when housekeeping lease is taken' do
        it 'returns conflict' do
          expect(housekeeping).to receive(:execute).once.and_raise(Projects::HousekeepingService::LeaseTaken)

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

          expect(response).to have_http_status(409)
          expect(json_response['message']).to match(/Somebody already triggered housekeeping for this project/)
        end
      end
    end

    context 'when authenticated as developer' do
      before do
1667
        project_member
1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684
      end

      it 'returns forbidden error' do
        post api("/projects/#{project.id}/housekeeping", user3)

        expect(response).to have_http_status(403)
      end
    end

    context 'when unauthenticated' do
      it 'returns authentication error' do
        post api("/projects/#{project.id}/housekeeping")

        expect(response).to have_http_status(401)
      end
    end
  end
N
Nihad Abbasov 已提交
1685
end