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

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

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

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

20 21 22 23 24 25 26 27
      expect(response).to have_gitlab_http_status(:ok)
      expect(json_response).to eq({})

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

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

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

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

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

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

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

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
  shared_context 'with language detection' do
    let(:ruby) { create(:programming_language, name: 'Ruby') }
    let(:javascript) { create(:programming_language, name: 'JavaScript') }
    let(:html) { create(:programming_language, name: 'HTML') }

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

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

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

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

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

        expect(response).to include_security_headers
      end
121 122
    end

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

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

135 136 137
        # TODO: We're currently querying to detect if a project is a fork
        # in 2 ways. Lower this back to 8 when `ForkedProjectLink` relation is
        # removed
138 139
        expect do
          get api('/projects', current_user)
140
        end.not_to exceed_query_limit(control).with_threshold(9)
141 142 143
      end
    end

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

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

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

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

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

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

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

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

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

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

199
        expect(response.status).to eq 200
200
        expect(response).to include_pagination_headers
201 202 203 204
        expect(json_response).to be_an Array
        expect(json_response.first.keys).to include('open_issues_count')
      end

205 206 207 208 209 210 211 212 213 214
      it 'does not include projects marked for deletion' do
        project.update(pending_delete: true)

        get api('/projects', user)

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

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

        get api('/projects', user)
219

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

226 227 228 229 230 231 232 233 234 235 236 237 238
      context 'and with_issues_enabled=true' do
        it 'only returns projects with issues enabled' do
          project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)

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

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

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

242
        expect(response).to have_gitlab_http_status(200)
243
        expect(response).to include_pagination_headers
244 245
        expect(json_response).to be_an Array
        expect(json_response.first).not_to include('statistics')
246 247
      end

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

251
        expect(response).to have_gitlab_http_status(200)
252
        expect(response).to include_pagination_headers
253 254
        expect(json_response).to be_an Array
        expect(json_response.first).to include 'statistics'
255 256
      end

257 258 259 260 261 262 263 264 265 266
      it "does not include license by default" do
        get api('/projects', user)

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

      it "does not include license if requested" do
blackst0ne's avatar
blackst0ne committed
267
        get api('/projects', user), params: { license: true }
268 269 270 271 272 273 274

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

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

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

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

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

321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
      context 'and using archived' do
        let!(:archived_project) { create(:project, creator_id: user.id, namespace: user.namespace, archived: true) }

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

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

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

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

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

          expect(response).to have_gitlab_http_status(200)
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.map { |project| project['id'] }).to contain_exactly(*Project.public_or_visible_to_user(user).pluck(:id))
        end
      end

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

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

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

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

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

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

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

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

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

401 402 403 404 405 406 407 408 409 410 411 412 413
      context 'and using the programming language filter' do
        include_context 'with language detection'

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

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

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

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

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

429
          expect(response).to have_gitlab_http_status(200)
430
          expect(response).to include_pagination_headers
431 432 433
          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)
434
        end
435 436
      end

437
      context 'and with starred=true' do
438
        let(:public_project) { create(:project, :public) }
439

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

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

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

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

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

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

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

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

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

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

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

        it 'returns an array of groups the user has at least developer access' do
blackst0ne's avatar
blackst0ne committed
506
          get api('/projects', user2), params: { min_access_level: 30 }
507 508 509 510 511 512
          expect(response).to have_gitlab_http_status(200)
          expect(response).to include_pagination_headers
          expect(json_response).to be_an Array
          expect(json_response.map { |project| project['id'] }).to contain_exactly(project2.id, project3.id)
        end
      end
513
    end
514

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

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

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

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

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

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

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

      project = Project.first

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

    it 'creates new project without name but with path and returns 201' do
blackst0ne's avatar
blackst0ne committed
567
      expect { post api('/projects', user), params: { path: 'foo_project' } }
568
        .to change { Project.count }.by(1)
569
      expect(response).to have_gitlab_http_status(201)
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

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

      project = Project.first

      expect(project.name).to eq('Foo Project')
585
      expect(project.path).to eq('path-project-Foo')
586 587
    end

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

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

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

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

616
      expect(response).to have_gitlab_http_status(201)
winniehell's avatar
winniehell committed
617

618
      project.each_pair do |k, v|
619
        next if %i[has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled storage_version].include?(k)
620

621
        expect(json_response[k.to_s]).to eq(v)
Alex Denisov's avatar
Alex Denisov committed
622
      end
623 624 625 626 627 628

      # 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)
629
    end
630

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

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

      expect(json_response['visibility']).to eq('public')
637 638
    end

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

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

      expect(json_response['visibility']).to eq('internal')
645 646
    end

647
    it 'sets a project as private' do
648 649
      project = attributes_for(:project, visibility: 'private')

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

      expect(json_response['visibility']).to eq('private')
653 654
    end

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

blackst0ne's avatar
blackst0ne committed
658
      post api('/projects', user), params: project
Steve's avatar
Steve committed
659 660 661 662

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

663 664 665
    it 'sets tag list to a project' do
      project = attributes_for(:project, tag_list: %w[tagFirst tagSecond])

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

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

671
    it 'uploads avatar for project a project' do
672
      project = attributes_for(:project, avatar: fixture_file_upload('spec/fixtures/banana_sample.gif', 'image/gif'))
673

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

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

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

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

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

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

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

693
      expect(json_response['resolve_outdated_diff_discussions']).to be_truthy
694 695
    end

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

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

701
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_falsey
702 703
    end

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

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

709
      expect(json_response['only_allow_merge_if_pipeline_succeeds']).to be_truthy
710 711
    end

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

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

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

720 721 722
    it 'sets a project as allowing merge if only_allow_merge_if_all_discussions_are_resolved is nil' do
      project = attributes_for(:project, only_allow_merge_if_all_discussions_are_resolved: nil)

blackst0ne's avatar
blackst0ne committed
723
      post api('/projects', user), params: project
724 725 726 727

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

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

blackst0ne's avatar
blackst0ne committed
731
      post api('/projects', user), params: project
732 733 734 735

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

736 737 738
    it 'sets the merge method of a project to rebase merge' do
      project = attributes_for(:project, merge_method: 'rebase_merge')

blackst0ne's avatar
blackst0ne committed
739
      post api('/projects', user), params: project
740 741 742 743 744 745 746

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

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

blackst0ne's avatar
blackst0ne committed
747
      post api('/projects', user), params: project
748 749 750 751

      expect(response).to have_gitlab_http_status(400)
    end

752
    it 'ignores import_url when it is nil' do
Sean McGivern's avatar
Sean McGivern committed
753
      project = attributes_for(:project, import_url: nil)
754

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

757
      expect(response).to have_gitlab_http_status(201)
758 759
    end

760
    context 'when a visibility level is restricted' do
761
      let(:project_param) { attributes_for(:project, visibility: 'public') }
762

763
      before do
764
        stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
765 766
      end

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

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

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

779
        expect(json_response['visibility']).to eq('public')
780 781
      end
    end
782 783
  end

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

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

790
      expect(response).to have_gitlab_http_status(404)
vanadium23's avatar
vanadium23 committed
791 792 793
      expect(json_response['message']).to eq('404 User Not Found')
    end

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

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

803 804 805 806
    it 'returns projects filtered by username' do
      get api("/users/#{user4.username}/projects/", user)

      expect(response).to have_gitlab_http_status(200)
vanadium23's avatar
vanadium23 committed
807 808 809 810
      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
811

812
    it 'returns projects filtered by minimal access level' do
813 814 815 816 817
      private_project1 = create(:project, :private, name: 'private_project1', creator_id: user4.id, namespace: user4.namespace)
      private_project2 = create(:project, :private, name: 'private_project2', creator_id: user4.id, namespace: user4.namespace)
      private_project1.add_developer(user2)
      private_project2.add_reporter(user2)

blackst0ne's avatar
blackst0ne committed
818
      get api("/users/#{user4.id}/projects/", user2), params: { min_access_level: 30 }
819 820 821 822 823 824

      expect(response).to have_gitlab_http_status(200)
      expect(response).to include_pagination_headers
      expect(json_response).to be_an Array
      expect(json_response.map { |project| project['id'] }).to contain_exactly(private_project1.id)
    end
825 826 827 828 829 830 831 832 833 834 835 836 837

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

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

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

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

845
      project = Project.last
846 847 848 849 850 851

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

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

856
      project = Project.last
857 858 859

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

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

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

870
    it 'assigns attributes to project' do
Angus MacArthur's avatar
Angus MacArthur committed
871
      project = attributes_for(:project, {
872 873
        issues_enabled: false,
        merge_requests_enabled: false,
874
        wiki_enabled: false,
875 876
        request_access_enabled: true,
        jobs_enabled: true
Angus MacArthur's avatar
Angus MacArthur committed
877 878
      })

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

881
      expect(response).to have_gitlab_http_status(201)
882

883
      project.each_pair do |k, v|
884
        next if %i[has_external_issue_tracker path storage_version].include?(k)
885

886
        expect(json_response[k.to_s]).to eq(v)
Angus MacArthur's avatar
Angus MacArthur committed
887 888
      end
    end
889

890
    it 'sets a project as public' do
891
      project = attributes_for(:project, visibility: 'public')
892

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

895
      expect(response).to have_gitlab_http_status(201)
896
      expect(json_response['visibility']).to eq('public')
897
    end
898

899
    it 'sets a project as internal' do
900 901
      project = attributes_for(:project, visibility: 'internal')

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