project_spec.rb 76.8 KB
Newer Older
gitlabhq's avatar
gitlabhq committed
1
2
require 'spec_helper'

3
describe Project do
4
  describe 'associations' do
5
6
7
8
    it { is_expected.to belong_to(:group) }
    it { is_expected.to belong_to(:namespace) }
    it { is_expected.to belong_to(:creator).class_name('User') }
    it { is_expected.to have_many(:users) }
ubudzisz's avatar
ubudzisz committed
9
    it { is_expected.to have_many(:services) }
10
11
12
13
14
    it { is_expected.to have_many(:events) }
    it { is_expected.to have_many(:merge_requests) }
    it { is_expected.to have_many(:issues) }
    it { is_expected.to have_many(:milestones) }
    it { is_expected.to have_many(:project_members).dependent(:delete_all) }
15
    it { is_expected.to have_many(:users).through(:project_members) }
16
17
18
19
    it { is_expected.to have_many(:requesters).dependent(:delete_all) }
    it { is_expected.to have_many(:notes) }
    it { is_expected.to have_many(:snippets).class_name('ProjectSnippet') }
    it { is_expected.to have_many(:deploy_keys_projects) }
20
    it { is_expected.to have_many(:deploy_keys) }
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    it { is_expected.to have_many(:hooks) }
    it { is_expected.to have_many(:protected_branches) }
    it { is_expected.to have_one(:forked_project_link) }
    it { is_expected.to have_one(:slack_service) }
    it { is_expected.to have_one(:microsoft_teams_service) }
    it { is_expected.to have_one(:mattermost_service) }
    it { is_expected.to have_one(:pushover_service) }
    it { is_expected.to have_one(:asana_service) }
    it { is_expected.to have_many(:boards) }
    it { is_expected.to have_one(:campfire_service) }
    it { is_expected.to have_one(:drone_ci_service) }
    it { is_expected.to have_one(:emails_on_push_service) }
    it { is_expected.to have_one(:pipelines_email_service) }
    it { is_expected.to have_one(:irker_service) }
    it { is_expected.to have_one(:pivotaltracker_service) }
    it { is_expected.to have_one(:hipchat_service) }
    it { is_expected.to have_one(:flowdock_service) }
    it { is_expected.to have_one(:assembla_service) }
    it { is_expected.to have_one(:slack_slash_commands_service) }
    it { is_expected.to have_one(:mattermost_slash_commands_service) }
    it { is_expected.to have_one(:gemnasium_service) }
    it { is_expected.to have_one(:buildkite_service) }
    it { is_expected.to have_one(:bamboo_service) }
    it { is_expected.to have_one(:teamcity_service) }
    it { is_expected.to have_one(:jira_service) }
    it { is_expected.to have_one(:redmine_service) }
    it { is_expected.to have_one(:custom_issue_tracker_service) }
    it { is_expected.to have_one(:bugzilla_service) }
    it { is_expected.to have_one(:gitlab_issue_tracker_service) }
    it { is_expected.to have_one(:external_wiki_service) }
    it { is_expected.to have_one(:project_feature) }
    it { is_expected.to have_one(:statistics).class_name('ProjectStatistics') }
    it { is_expected.to have_one(:import_data).class_name('ProjectImportData') }
ubudzisz's avatar
ubudzisz committed
54
55
    it { is_expected.to have_one(:last_event).class_name('Event') }
    it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
56
    it { is_expected.to have_many(:commit_statuses) }
57
    it { is_expected.to have_many(:pipelines) }
58
59
60
    it { is_expected.to have_many(:builds) }
    it { is_expected.to have_many(:runner_projects) }
    it { is_expected.to have_many(:runners) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
61
    it { is_expected.to have_many(:active_runners) }
62
63
    it { is_expected.to have_many(:variables) }
    it { is_expected.to have_many(:triggers) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
64
    it { is_expected.to have_many(:pages_domains) }
65
66
67
68
69
70
71
72
73
    it { is_expected.to have_many(:labels).class_name('ProjectLabel') }
    it { is_expected.to have_many(:users_star_projects) }
    it { is_expected.to have_many(:environments) }
    it { is_expected.to have_many(:deployments) }
    it { is_expected.to have_many(:todos) }
    it { is_expected.to have_many(:releases) }
    it { is_expected.to have_many(:lfs_objects_projects) }
    it { is_expected.to have_many(:project_group_links) }
    it { is_expected.to have_many(:notification_settings).dependent(:delete_all) }
ubudzisz's avatar
ubudzisz committed
74
    it { is_expected.to have_many(:forks).through(:forked_project_links) }
75
    it { is_expected.to have_many(:uploads).dependent(:destroy) }
76
    it { is_expected.to have_many(:pipeline_schedules) }
77

78
79
    context 'after initialized' do
      it "has a project_feature" do
80
        expect(described_class.new.project_feature).to be_present
81
82
83
      end
    end

84
    describe '#members & #requesters' do
85
      let(:project) { create(:project, :public, :access_requestable) }
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
      let(:requester) { create(:user) }
      let(:developer) { create(:user) }
      before do
        project.request_access(requester)
        project.team << [developer, :developer]
      end

      describe '#members' do
        it 'includes members and exclude requesters' do
          member_user_ids = project.members.pluck(:user_id)

          expect(member_user_ids).to include(developer.id)
          expect(member_user_ids).not_to include(requester.id)
        end
      end

      describe '#requesters' do
        it 'does not include requesters' do
          requester_user_ids = project.requesters.pluck(:user_id)

          expect(requester_user_ids).to include(requester.id)
          expect(requester_user_ids).not_to include(developer.id)
        end
      end
    end
111
112
113
114
115

    describe '#boards' do
      it 'raises an error when attempting to add more than one board to the project' do
        subject.boards.build

116
        expect { subject.boards.build }.to raise_error(Project::BoardLimitExceeded, 'Number of permitted boards exceeded')
117
118
119
        expect(subject.boards.size).to eq 1
      end
    end
gitlabhq's avatar
gitlabhq committed
120
121
  end

122
123
124
125
126
127
  describe 'modules' do
    subject { described_class }

    it { is_expected.to include_module(Gitlab::ConfigHelper) }
    it { is_expected.to include_module(Gitlab::ShellAdapter) }
    it { is_expected.to include_module(Gitlab::VisibilityLevel) }
128
    it { is_expected.to include_module(Gitlab::CurrentSettings) }
129
130
    it { is_expected.to include_module(Referable) }
    it { is_expected.to include_module(Sortable) }
131
132
  end

133
  describe 'validation' do
134
    let!(:project) { create(:project) }
135

136
137
    it { is_expected.to validate_presence_of(:name) }
    it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) }
138
    it { is_expected.to validate_length_of(:name).is_at_most(255) }
139

140
141
    it { is_expected.to validate_presence_of(:path) }
    it { is_expected.to validate_uniqueness_of(:path).scoped_to(:namespace_id) }
142
143
144
145
    it { is_expected.to validate_length_of(:path).is_at_most(255) }

    it { is_expected.to validate_length_of(:description).is_at_most(2000) }

146
147
148
    it { is_expected.to validate_length_of(:ci_config_path).is_at_most(255) }
    it { is_expected.to allow_value('').for(:ci_config_path) }
    it { is_expected.not_to allow_value('test/../foo').for(:ci_config_path) }
149

150
    it { is_expected.to validate_presence_of(:creator) }
151

152
    it { is_expected.to validate_presence_of(:namespace) }
153

154
    it { is_expected.to validate_presence_of(:repository_storage) }
155

156
    it 'does not allow new projects beyond user limits' do
157
      project2 = build(:project)
158
159
      allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object)
      expect(project2).not_to be_valid
Phil Hughes's avatar
Phil Hughes committed
160
      expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/)
161
    end
162
163
164

    describe 'wiki path conflict' do
      context "when the new path has been used by the wiki of other Project" do
165
        it 'has an error on the name attribute' do
166
          new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki")
167
168
169
170
171
172
173

          expect(new_project).not_to be_valid
          expect(new_project.errors[:name].first).to eq('has already been taken')
        end
      end

      context "when the new wiki path has been used by the path of other Project" do
174
        it 'has an error on the name attribute' do
175
176
          project_with_wiki_suffix = create(:project, path: 'foo.wiki')
          new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo')
177
178
179
180
181
182

          expect(new_project).not_to be_valid
          expect(new_project.errors[:name].first).to eq('has already been taken')
        end
      end
    end
183
184

    context 'repository storages inclussion' do
185
      let(:project2) { build(:project, repository_storage: 'missing') }
186
187

      before do
188
        storages = { 'custom' => { 'path' => 'tmp/tests/custom_repositories' } }
189
190
191
        allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
      end

192
      it "does not allow repository storages that don't match a label in the configuration" do
193
194
195
196
        expect(project2).not_to be_valid
        expect(project2.errors[:repository_storage].first).to match(/is not included in the list/)
      end
    end
197

198
    it 'does not allow an invalid URI as import_url' do
199
      project2 = build(:project, import_url: 'invalid://')
James Lopez's avatar
James Lopez committed
200
201
202
203

      expect(project2).not_to be_valid
    end

204
    it 'does allow a valid URI as import_url' do
205
      project2 = build(:project, import_url: 'ssh://test@gitlab.com/project.git')
James Lopez's avatar
James Lopez committed
206

207
208
      expect(project2).to be_valid
    end
209

210
    it 'allows an empty URI' do
211
      project2 = build(:project, import_url: '')
212

213
      expect(project2).to be_valid
214
215
216
    end

    it 'does not produce import data on an empty URI' do
217
      project2 = build(:project, import_url: '')
218
219
220
221
222

      expect(project2.import_data).to be_nil
    end

    it 'does not produce import data on an invalid URI' do
223
      project2 = build(:project, import_url: 'test://')
224
225
226

      expect(project2.import_data).to be_nil
    end
227

228
    it "does not allow blocked import_url localhost" do
229
      project2 = build(:project, import_url: 'http://localhost:9000/t.git')
230
231
232
233
234
235

      expect(project2).to be_invalid
      expect(project2.errors[:import_url]).to include('imports are not allowed from that URL')
    end

    it "does not allow blocked import_url port" do
236
      project2 = build(:project, import_url: 'http://github.com:25/t.git')
237
238
239
240
241

      expect(project2).to be_invalid
      expect(project2.errors[:import_url]).to include('imports are not allowed from that URL')
    end

242
243
    describe 'project pending deletion' do
      let!(:project_pending_deletion) do
244
        create(:project,
245
246
247
               pending_delete: true)
      end
      let(:new_project) do
248
        build(:project,
249
250
251
252
253
254
255
256
257
258
259
260
              name: project_pending_deletion.name,
              namespace: project_pending_deletion.namespace)
      end

      before do
        new_project.validate
      end

      it 'contains errors related to the project being deleted' do
        expect(new_project.errors.full_messages.first).to eq('The project is still being deleted. Please try again later.')
      end
    end
261
262
263

    describe 'path validation' do
      it 'allows paths reserved on the root namespace' do
264
        project = build(:project, path: 'api')
265
266
267
268
269

        expect(project).to be_valid
      end

      it 'rejects paths reserved on another level' do
270
        project = build(:project, path: 'tree')
271
272
273

        expect(project).not_to be_valid
      end
274
275
276

      it 'rejects nested paths' do
        parent = create(:group, :nested, path: 'environments')
277
        project = build(:project, path: 'folders', namespace: parent)
278
279
280

        expect(project).not_to be_valid
      end
281
282
283

      it 'allows a reserved group name' do
        parent = create(:group)
284
        project = build(:project, path: 'avatar', namespace: parent)
285
286
287

        expect(project).to be_valid
      end
288
    end
gitlabhq's avatar
gitlabhq committed
289
  end
290

291
  describe 'project token' do
292
    it 'sets an random token if none provided' do
293
      project = FactoryGirl.create :project, runners_token: ''
Kamil Trzcinski's avatar
Kamil Trzcinski committed
294
      expect(project.runners_token).not_to eq('')
295
296
    end

ubudzisz's avatar
ubudzisz committed
297
    it 'does not set an random token if one provided' do
298
      project = FactoryGirl.create :project, runners_token: 'my-token'
Kamil Trzcinski's avatar
Kamil Trzcinski committed
299
      expect(project.runners_token).to eq('my-token')
300
301
    end
  end
gitlabhq's avatar
gitlabhq committed
302

303
  describe 'Respond to' do
304
305
306
307
308
    it { is_expected.to respond_to(:url_to_repo) }
    it { is_expected.to respond_to(:repo_exists?) }
    it { is_expected.to respond_to(:execute_hooks) }
    it { is_expected.to respond_to(:owner) }
    it { is_expected.to respond_to(:path_with_namespace) }
309
    it { is_expected.to respond_to(:full_path) }
gitlabhq's avatar
gitlabhq committed
310
311
  end

312
  describe 'delegation' do
313
314
315
316
317
318
319
320
    [:add_guest, :add_reporter, :add_developer, :add_master, :add_user, :add_users].each do |method|
      it { is_expected.to delegate_method(method).to(:team) }
    end

    it { is_expected.to delegate_method(:empty_repo?).to(:repository) }
    it { is_expected.to delegate_method(:members).to(:team).with_prefix(true) }
    it { is_expected.to delegate_method(:count).to(:forks).with_prefix(true) }
    it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) }
321
322
  end

323
  describe '#to_reference' do
324
    let(:owner)     { create(:user, name: 'Gitlab') }
325
    let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
326
    let(:project)   { create(:project, path: 'sample-project', namespace: namespace) }
327
    let(:group)     { create(:group, name: 'Group', path: 'sample-group', owner: owner) }
328

329
    context 'when nil argument' do
330
331
332
333
334
      it 'returns nil' do
        expect(project.to_reference).to be_nil
      end
    end

335
    context 'when full is true' do
336
      it 'returns complete path to the project' do
337
338
339
        expect(project.to_reference(full: true)).to          eq 'sample-namespace/sample-project'
        expect(project.to_reference(project, full: true)).to eq 'sample-namespace/sample-project'
        expect(project.to_reference(group, full: true)).to   eq 'sample-namespace/sample-project'
340
341
342
343
344
345
346
347
348
349
      end
    end

    context 'when same project argument' do
      it 'returns nil' do
        expect(project.to_reference(project)).to be_nil
      end
    end

    context 'when cross namespace project argument' do
350
      let(:another_namespace_project) { create(:project, name: 'another-project') }
351
352
353
354
355
356
357

      it 'returns complete path to the project' do
        expect(project.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project'
      end
    end

    context 'when same namespace / cross-project argument' do
358
      let(:another_project) { create(:project, namespace: namespace) }
359

360
      it 'returns path to the project' do
361
362
363
        expect(project.to_reference(another_project)).to eq 'sample-project'
      end
    end
364

365
366
    context 'when different namespace / cross-project argument' do
      let(:another_namespace) { create(:namespace, path: 'another-namespace', owner: owner) }
367
      let(:another_project)   { create(:project, path: 'another-project', namespace: another_namespace) }
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384

      it 'returns full path to the project' do
        expect(project.to_reference(another_project)).to eq 'sample-namespace/sample-project'
      end
    end

    context 'when argument is a namespace' do
      context 'with same project path' do
        it 'returns path to the project' do
          expect(project.to_reference(namespace)).to eq 'sample-project'
        end
      end

      context 'with different project path' do
        it 'returns full path to the project' do
          expect(project.to_reference(group)).to eq 'sample-namespace/sample-project'
        end
385
386
      end
    end
387
388
389
390
391
  end

  describe '#to_human_reference' do
    let(:owner) { create(:user, name: 'Gitlab') }
    let(:namespace) { create(:namespace, name: 'Sample namespace', owner: owner) }
392
    let(:project) { create(:project, name: 'Sample project', namespace: namespace) }
393
394
395
396
397
398
399
400
401
402
403
404
405
406

    context 'when nil argument' do
      it 'returns nil' do
        expect(project.to_human_reference).to be_nil
      end
    end

    context 'when same project argument' do
      it 'returns nil' do
        expect(project.to_human_reference(project)).to be_nil
      end
    end

    context 'when cross namespace project argument' do
407
      let(:another_namespace_project) { create(:project, name: 'another-project') }
408
409
410
411
412
413
414

      it 'returns complete name with namespace of the project' do
        expect(project.to_human_reference(another_namespace_project)).to eq 'Gitlab / Sample project'
      end
    end

    context 'when same namespace / cross-project argument' do
415
      let(:another_project) { create(:project, namespace: namespace) }
416
417
418
419

      it 'returns name of the project' do
        expect(project.to_human_reference(another_project)).to eq 'Sample project'
      end
420
421
422
    end
  end

423
  describe '#repository_storage_path' do
424
    let(:project) { create(:project, repository_storage: 'custom') }
425
426
427

    before do
      FileUtils.mkdir('tmp/tests/custom_repositories')
428
      storages = { 'custom' => { 'path' => 'tmp/tests/custom_repositories' } }
429
430
431
432
433
434
435
436
437
438
439
440
      allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
    end

    after do
      FileUtils.rm_rf('tmp/tests/custom_repositories')
    end

    it 'returns the repository storage path' do
      expect(project.repository_storage_path).to eq('tmp/tests/custom_repositories')
    end
  end

441
  it 'returns valid url to repo' do
442
    project = described_class.new(path: 'somewhere')
443
    expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
gitlabhq's avatar
gitlabhq committed
444
445
  end

Douwe Maan's avatar
Douwe Maan committed
446
  describe "#web_url" do
447
    let(:project) { create(:project, path: "somewhere") }
Douwe Maan's avatar
Douwe Maan committed
448
449

    it 'returns the full web URL for this repo' do
450
      expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere")
Douwe Maan's avatar
Douwe Maan committed
451
    end
452
453
  end

454
  describe "#new_issue_address" do
455
    let(:project) { create(:project, path: "somewhere") }
456
457
    let(:user) { create(:user) }

458
459
460
461
462
463
    context 'incoming email enabled' do
      before do
        stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
      end

      it 'returns the address to create a new issue' do
464
        address = "p+#{project.full_path}+#{user.incoming_email_token}@gl.ab"
465
466
467
468
469
470
471
472
473

        expect(project.new_issue_address(user)).to eq(address)
      end
    end

    context 'incoming email disabled' do
      before do
        stub_incoming_email_setting(enabled: false)
      end
474

475
476
477
      it 'returns nil' do
        expect(project.new_issue_address(user)).to be_nil
      end
478
479
480
    end
  end

481
  describe 'last_activity methods' do
482
483
    let(:timestamp) { 2.hours.ago }
    # last_activity_at gets set to created_at upon creation
484
    let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) }
gitlabhq's avatar
gitlabhq committed
485

486
    describe 'last_activity' do
487
      it 'alias last_activity to last_event' do
488
        last_event = create(:event, :closed, project: project)
489

490
        expect(project.last_activity).to eq(last_event)
491
      end
gitlabhq's avatar
gitlabhq committed
492
493
    end

494
495
    describe 'last_activity_date' do
      it 'returns the creation date of the project\'s last event if present' do
496
        new_event = create(:event, :closed, project: project, created_at: Time.now)
497

498
        project.reload
499
        expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i)
500
      end
501

502
      it 'returns the project\'s last update date if it has no events' do
503
        expect(project.last_activity_date).to eq(project.updated_at)
504
      end
505
506
    end
  end
507

508
  describe '#get_issue' do
509
    let(:project) { create(:project) }
Stan Hu's avatar
Stan Hu committed
510
    let!(:issue)  { create(:issue, project: project) }
511
512
513
514
515
    let(:user)    { create(:user) }

    before do
      project.team << [user, :developer]
    end
516
517
518

    context 'with default issues tracker' do
      it 'returns an issue' do
519
        expect(project.get_issue(issue.iid, user)).to eq issue
520
521
      end

Stan Hu's avatar
Stan Hu committed
522
523
524
525
      it 'returns count of open issues' do
        expect(project.open_issues_count).to eq(1)
      end

526
      it 'returns nil when no issue found' do
527
528
529
530
531
532
        expect(project.get_issue(999, user)).to be_nil
      end

      it "returns nil when user doesn't have access" do
        user = create(:user)
        expect(project.get_issue(issue.iid, user)).to eq nil
533
534
535
536
      end
    end

    context 'with external issues tracker' do
537
      let!(:internal_issue) { create(:issue, project: project) }
538
      before do
539
        allow(project).to receive(:external_issue_tracker).and_return(true)
540
541
      end

542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
      context 'when internal issues are enabled' do
        it 'returns interlan issue' do
          issue = project.get_issue(internal_issue.iid, user)

          expect(issue).to be_kind_of(Issue)
          expect(issue.iid).to eq(internal_issue.iid)
          expect(issue.project).to eq(project)
        end

        it 'returns an ExternalIssue when internal issue does not exists' do
          issue = project.get_issue('FOO-1234', user)

          expect(issue).to be_kind_of(ExternalIssue)
          expect(issue.iid).to eq('FOO-1234')
          expect(issue.project).to eq(project)
        end
      end

      context 'when internal issues are disabled' do
        before do
          project.issues_enabled = false
          project.save!
        end

        it 'returns always an External issues' do
          issue = project.get_issue(internal_issue.iid, user)
          expect(issue).to be_kind_of(ExternalIssue)
          expect(issue.iid).to eq(internal_issue.iid.to_s)
          expect(issue.project).to eq(project)
        end

        it 'returns an ExternalIssue when internal issue does not exists' do
          issue = project.get_issue('FOO-1234', user)
          expect(issue).to be_kind_of(ExternalIssue)
          expect(issue.iid).to eq('FOO-1234')
          expect(issue.project).to eq(project)
        end
579
580
581
582
583
      end
    end
  end

  describe '#issue_exists?' do
584
    let(:project) { create(:project) }
585
586
587
588
589
590
591
592
593
594
595
596

    it 'is truthy when issue exists' do
      expect(project).to receive(:get_issue).and_return(double)
      expect(project.issue_exists?(1)).to be_truthy
    end

    it 'is falsey when issue does not exist' do
      expect(project).to receive(:get_issue).and_return(nil)
      expect(project.issue_exists?(1)).to be_falsey
    end
  end

597
  describe '#to_param' do
598
599
600
    context 'with namespace' do
      before do
        @group = create :group, name: 'gitlab'
601
        @project = create(:project, name: 'gitlabhq', namespace: @group)
602
603
      end

Vinnie Okada's avatar
Vinnie Okada committed
604
      it { expect(@project.to_param).to eq('gitlabhq') }
605
    end
606
607
608

    context 'with invalid path' do
      it 'returns previous path to keep project suitable for use in URLs when persisted' do
609
        project = create(:project, path: 'gitlab')
610
611
612
613
614
615
616
        project.path = 'foo&bar'

        expect(project).not_to be_valid
        expect(project.to_param).to eq 'gitlab'
      end

      it 'returns current path when new record' do
617
        project = build(:project, path: 'gitlab')
618
619
620
621
622
623
        project.path = 'foo&bar'

        expect(project).not_to be_valid
        expect(project.to_param).to eq 'foo&bar'
      end
    end
624
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
625

626
  describe '#repository' do
627
    let(:project) { create(:project, :repository) }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
628

629
    it 'returns valid repo' do
630
      expect(project.repository).to be_kind_of(Repository)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
631
632
    end
  end
633

634
  describe '#default_issues_tracker?' do
635
    it "is true if used internal tracker" do
636
      project = build(:project)
637

638
      expect(project.default_issues_tracker?).to be_truthy
639
640
    end

641
    it "is false if used other tracker" do
642
643
644
645
      # NOTE: The current nature of this factory requires persistence
      project = create(:redmine_project)

      expect(project.default_issues_tracker?).to be_falsey
646
647
648
    end
  end

649
  describe '#external_issue_tracker' do
650
    let(:project) { create(:project) }
651
652
653
    let(:ext_project) { create(:redmine_project) }

    context 'on existing projects with no value for has_external_issue_tracker' do
654
      before do
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
        project.update_column(:has_external_issue_tracker, nil)
        ext_project.update_column(:has_external_issue_tracker, nil)
      end

      it 'updates the has_external_issue_tracker boolean' do
        expect do
          project.external_issue_tracker
        end.to change { project.reload.has_external_issue_tracker }.to(false)

        expect do
          ext_project.external_issue_tracker
        end.to change { ext_project.reload.has_external_issue_tracker }.to(true)
      end
    end

    it 'returns nil and does not query services when there is no external issue tracker' do
      expect(project).not_to receive(:services)

      expect(project.external_issue_tracker).to eq(nil)
    end

    it 'retrieves external_issue_tracker querying services and cache it when there is external issue tracker' do
      ext_project.reload # Factory returns a project with changed attributes
      expect(ext_project).to receive(:services).once.and_call_original

      2.times { expect(ext_project.external_issue_tracker).to be_a_kind_of(RedmineService) }
    end
  end

684
  describe '#cache_has_external_issue_tracker' do
685
    let(:project) { create(:project, has_external_issue_tracker: nil) }
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705

    it 'stores true if there is any external_issue_tracker' do
      services = double(:service, external_issue_trackers: [RedmineService.new])
      expect(project).to receive(:services).and_return(services)

      expect do
        project.cache_has_external_issue_tracker
      end.to change { project.has_external_issue_tracker}.to(true)
    end

    it 'stores false if there is no external_issue_tracker' do
      services = double(:service, external_issue_trackers: [])
      expect(project).to receive(:services).and_return(services)

      expect do
        project.cache_has_external_issue_tracker
      end.to change { project.has_external_issue_tracker}.to(false)
    end
  end

706
  describe '#has_wiki?' do
707
708
709
    let(:no_wiki_project)       { create(:project, :wiki_disabled, has_external_wiki: false) }
    let(:wiki_enabled_project)  { create(:project) }
    let(:external_wiki_project) { create(:project, has_external_wiki: true) }
710
711
712
713
714
715
716
717

    it 'returns true if project is wiki enabled or has external wiki' do
      expect(wiki_enabled_project).to have_wiki
      expect(external_wiki_project).to have_wiki
      expect(no_wiki_project).not_to have_wiki
    end
  end

718
  describe '#external_wiki' do
719
    let(:project) { create(:project) }
720

721
722
723
724
725
    context 'with an active external wiki' do
      before do
        create(:service, project: project, type: 'ExternalWikiService', active: true)
        project.external_wiki
      end
726

727
728
729
      it 'sets :has_external_wiki as true' do
        expect(project.has_external_wiki).to be(true)
      end
730

731
732
      it 'sets :has_external_wiki as false if an external wiki service is destroyed later' do
        expect(project.has_external_wiki).to be(true)
733

734
735
736
737
        project.services.external_wikis.first.destroy

        expect(project.has_external_wiki).to be(false)
      end
738
739
    end

740
741
742
743
    context 'with an inactive external wiki' do
      before do
        create(:service, project: project, type: 'ExternalWikiService', active: false)
      end
744

745
746
747
      it 'sets :has_external_wiki as false' do
        expect(project.has_external_wiki).to be(false)
      end
748
749
    end

750
751
752
753
    context 'with no external wiki' do
      before do
        project.external_wiki
      end
754

755
756
757
758
759
760
761
762
763
764
765
      it 'sets :has_external_wiki as false' do
        expect(project.has_external_wiki).to be(false)
      end

      it 'sets :has_external_wiki as true if an external wiki service is created later' do
        expect(project.has_external_wiki).to be(false)

        create(:service, project: project, type: 'ExternalWikiService', active: true)

        expect(project.has_external_wiki).to be(true)
      end
766
767
768
    end
  end

769
770
  describe '#star_count' do
    it 'counts stars from multiple users' do
Ciro Santilli's avatar
Ciro Santilli committed
771
772
      user1 = create :user
      user2 = create :user
773
      project = create(:project, :public)
Ciro Santilli's avatar
Ciro Santilli committed
774
775

      expect(project.star_count).to eq(0)
776

Ciro Santilli's avatar
Ciro Santilli committed
777
      user1.toggle_star(project)
778
779
      expect(project.reload.star_count).to eq(1)

Ciro Santilli's avatar
Ciro Santilli committed
780
      user2.toggle_star(project)
781
782
783
      project.reload
      expect(project.reload.star_count).to eq(2)

Ciro Santilli's avatar
Ciro Santilli committed
784
      user1.toggle_star(project)
785
786
787
      project.reload
      expect(project.reload.star_count).to eq(1)

Ciro Santilli's avatar
Ciro Santilli committed
788
      user2.toggle_star(project)
789
790
791
792
      project.reload
      expect(project.reload.star_count).to eq(0)
    end

793
    it 'counts stars on the right project' do
794
      user = create :user
795
796
      project1 = create(:project, :public)
      project2 = create(:project, :public)
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823

      expect(project1.star_count).to eq(0)
      expect(project2.star_count).to eq(0)

      user.toggle_star(project1)
      project1.reload
      project2.reload
      expect(project1.star_count).to eq(1)
      expect(project2.star_count).to eq(0)

      user.toggle_star(project1)
      project1.reload
      project2.reload
      expect(project1.star_count).to eq(0)
      expect(project2.star_count).to eq(0)

      user.toggle_star(project2)
      project1.reload
      project2.reload
      expect(project1.star_count).to eq(0)
      expect(project2.star_count).to eq(1)

      user.toggle_star(project2)
      project1.reload
      project2.reload
      expect(project1.star_count).to eq(0)
      expect(project2.star_count).to eq(0)
Ciro Santilli's avatar
Ciro Santilli committed
824
825
    end
  end
826

827
  describe '#avatar_type' do
828
    let(:project) { create(:project) }
829

830
    it 'is true if avatar is image' do
831
      project.update_attribute(:avatar, 'uploads/avatar.png')
832
      expect(project.avatar_type).to be_truthy
833
834
    end

835
    it 'is false if avatar is html page' do
836
      project.update_attribute(:avatar, 'uploads/avatar.html')
837
      expect(project.avatar_type).to eq(['only images allowed'])
838
839
    end
  end
sue445's avatar
sue445 committed
840

841
  describe '#avatar_url' do
sue445's avatar
sue445 committed
842
843
    subject { project.avatar_url }

844
    let(:project) { create(:project) }
sue445's avatar
sue445 committed
845

846
    context 'when avatar file is uploaded' do
847
      let(:project) { create(:project, :with_avatar) }
848
      let(:avatar_path) { "/uploads/-/system/project/avatar/#{project.id}/dk.png" }
849
      let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" }
sue445's avatar
sue445 committed
850

851
852
853
854
855
856
857
858
      it 'shows correct url' do
        expect(project.avatar_url).to eq(avatar_path)
        expect(project.avatar_url(only_path: false)).to eq([gitlab_host, avatar_path].join)

        allow(ActionController::Base).to receive(:asset_host).and_return(gitlab_host)

        expect(project.avatar_url).to eq([gitlab_host, avatar_path].join)
      end
sue445's avatar
sue445 committed
859
860
861
862
863
864
865
    end

    context 'When avatar file in git' do
      before do
        allow(project).to receive(:avatar_in_git) { true }
      end

866
      let(:avatar_path) { "/#{project.full_path}/avatar" }
sue445's avatar
sue445 committed
867

868
      it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
sue445's avatar
sue445 committed
869
    end
870
871

    context 'when git repo is empty' do
872
      let(:project) { create(:project) }
873

874
      it { is_expected.to eq nil }
875
    end
sue445's avatar
sue445 committed
876
  end
877

878
  describe '#pipeline_for' do
879
    let(:project) { create(:project, :repository) }
880
    let!(:pipeline) { create_pipeline }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
881

882
883
    shared_examples 'giving the correct pipeline' do
      it { is_expected.to eq(pipeline) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
884

885
886
      context 'return latest' do
        let!(:pipeline2) { create_pipeline }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
887

888
        it { is_expected.to eq(pipeline2) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
889
      end
890
891
892
893
894
895
896
897
898
899
900
901
902
    end

    context 'with explicit sha' do
      subject { project.pipeline_for('master', pipeline.sha) }

      it_behaves_like 'giving the correct pipeline'
    end

    context 'with implicit sha' do
      subject { project.pipeline_for('master') }

      it_behaves_like 'giving the correct pipeline'
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
903

904
905
906
907
908
    def create_pipeline
      create(:ci_pipeline,
             project: project,
             ref: 'master',
             sha: project.commit('master').sha)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
909
    end
910
  end
911

912
  describe '#builds_enabled' do
913
    let(:project) { create(:project) }
914

915
916
917
    subject { project.builds_enabled }

    it { expect(project.builds_enabled?).to be_truthy }
918
  end
919

920
  describe '.with_shared_runners' do
921
    subject { described_class.with_shared_runners }
922
923

    context 'when shared runners are enabled for project' do
924
      let!(:project) { create(:project, shared_runners_enabled: true) }
925
926
927
928
929
930
931

      it "returns a project" do
        is_expected.to eq([project])
      end
    end

    context 'when shared runners are disabled for project' do
932
      let!(:project) { create(:project, shared_runners_enabled: false) }
933
934
935
936
937
938
939

      it "returns an empty array" do
        is_expected.to be_empty
      end
    end
  end

940
  describe '.cached_count', :use_clean_rails_memory_store_caching do
941
    let(:group)     { create(:group, :public) }
942
943
    let!(:project1) { create(:project, :public, group: group) }
    let!(:project2) { create(:project, :public, group: group) }
944
945

    it 'returns total project count' do
946
      expect(described_class).to receive(:count).once.and_call_original
947
948

      3.times do
949
        expect(described_class.cached_count).to eq(2)
950
951
952
953
      end
    end
  end

954
  describe '.trending' do
Felipe Artur's avatar
Felipe Artur committed
955
    let(:group)    { create(:group, :public) }
956
957
    let(:project1) { create(:project, :public, group: group) }
    let(:project2) { create(:project, :public, group: group) }
958
959
960
961
962
963
964
965

    before do
      2.times do
        create(:note_on_commit, project: project1)
      end

      create(:note_on_commit, project: project2)

966
      TrendingProject.refresh!
967
968
    end

969
    subject { described_class.trending.to_a }
970

971
972
    it 'sorts projects by the amount of notes in descending order' do
      expect(subject).to eq([project1, project2])
973
    end
974
975
976
977
978
979
980
981

    it 'does not take system notes into account' do
      10.times do
        create(:note_on_commit, project: project2, system: true)
      end

      expect(described_class.trending.to_a).to eq([project1, project2])
    end
982
  end
Yorick Peterse's avatar
Yorick Peterse committed
983

Toon Claes's avatar
Toon Claes committed
984
985
986
987
  describe '.starred_by' do
    it 'returns only projects starred by the given user' do
      user1 = create(:user)
      user2 = create(:user)
988
989
990
      project1 = create(:project)
      project2 = create(:project)
      create(:project)
Toon Claes's avatar
Toon Claes committed
991
992
993
      user1.toggle_star(project1)
      user2.toggle_star(project2)

994
      expect(described_class.starred_by(user1)).to contain_exactly(project1)
Toon Claes's avatar
Toon Claes committed
995
996
997
    end
  end

Yorick Peterse's avatar
Yorick Peterse committed
998
  describe '.visible_to_user' do
999
    let!(:project) { create(:project, :private) }
Yorick Peterse's avatar
Yorick Peterse committed
1000
1001
1002
1003
1004
1005
    let!(:user)    { create(:user) }

    subject { described_class.visible_to_user(user) }

    describe 'when a user has access to a project' do
      before do
1006
        project.add_user(user, Gitlab::Access::MASTER)
Yorick Peterse's avatar
Yorick Peterse committed
1007
1008
1009
1010
1011
1012
1013
1014
1015
      end

      it { is_expected.to eq([project]) }
    end

    describe 'when a user does not have access to any projects' do
      it { is_expected.to eq([]) }
    end
  end
1016

1017
  context 'repository storage by default' do
1018
    let(:project) { create(:project) }
1019
1020

    before do
<