Read about our upcoming Code of Conduct on this issue

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

Douwe Maan's avatar
Douwe Maan committed
3
describe Project, models: true 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).dependent(:destroy) }
    it { is_expected.to have_many(:merge_requests).dependent(:destroy) }
    it { is_expected.to have_many(:issues).dependent(:destroy) }
    it { is_expected.to have_many(:milestones).dependent(:destroy) }
    it { is_expected.to have_many(:project_members).dependent(:destroy) }
15
16
    it { is_expected.to have_many(:users).through(:project_members) }
    it { is_expected.to have_many(:requesters).dependent(:destroy) }
17
18
19
20
21
22
23
    it { is_expected.to have_many(:notes).dependent(:destroy) }
    it { is_expected.to have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) }
    it { is_expected.to have_many(:deploy_keys_projects).dependent(:destroy) }
    it { is_expected.to have_many(:deploy_keys) }
    it { is_expected.to have_many(:hooks).dependent(:destroy) }
    it { is_expected.to have_many(:protected_branches).dependent(:destroy) }
    it { is_expected.to have_one(:forked_project_link).dependent(:destroy) }
24
    it { is_expected.to have_one(:slack_service).dependent(:destroy) }
25
    it { is_expected.to have_one(:mattermost_service).dependent(:destroy) }
26
27
    it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
    it { is_expected.to have_one(:asana_service).dependent(:destroy) }
28
    it { is_expected.to have_many(:boards).dependent(:destroy) }
ubudzisz's avatar
ubudzisz committed
29
30
31
32
33
34
35
36
37
38
    it { is_expected.to have_one(:campfire_service).dependent(:destroy) }
    it { is_expected.to have_one(:drone_ci_service).dependent(:destroy) }
    it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) }
    it { is_expected.to have_one(:builds_email_service).dependent(:destroy) }
    it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) }
    it { is_expected.to have_one(:irker_service).dependent(:destroy) }
    it { is_expected.to have_one(:pivotaltracker_service).dependent(:destroy) }
    it { is_expected.to have_one(:hipchat_service).dependent(:destroy) }
    it { is_expected.to have_one(:flowdock_service).dependent(:destroy) }
    it { is_expected.to have_one(:assembla_service).dependent(:destroy) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
39
    it { is_expected.to have_one(:slack_slash_commands_service).dependent(:destroy) }
40
    it { is_expected.to have_one(:mattermost_slash_commands_service).dependent(:destroy) }
ubudzisz's avatar
ubudzisz committed
41
42
43
44
45
46
47
48
49
50
51
    it { is_expected.to have_one(:gemnasium_service).dependent(:destroy) }
    it { is_expected.to have_one(:buildkite_service).dependent(:destroy) }
    it { is_expected.to have_one(:bamboo_service).dependent(:destroy) }
    it { is_expected.to have_one(:teamcity_service).dependent(:destroy) }
    it { is_expected.to have_one(:jira_service).dependent(:destroy) }
    it { is_expected.to have_one(:redmine_service).dependent(:destroy) }
    it { is_expected.to have_one(:custom_issue_tracker_service).dependent(:destroy) }
    it { is_expected.to have_one(:bugzilla_service).dependent(:destroy) }
    it { is_expected.to have_one(:gitlab_issue_tracker_service).dependent(:destroy) }
    it { is_expected.to have_one(:external_wiki_service).dependent(:destroy) }
    it { is_expected.to have_one(:project_feature).dependent(:destroy) }
Markus Koller's avatar
Markus Koller committed
52
    it { is_expected.to have_one(:statistics).class_name('ProjectStatistics').dependent(:delete) }
ubudzisz's avatar
ubudzisz committed
53
54
55
    it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:destroy) }
    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
61
62
    it { is_expected.to have_many(:builds) }
    it { is_expected.to have_many(:runner_projects) }
    it { is_expected.to have_many(:runners) }
    it { is_expected.to have_many(:variables) }
    it { is_expected.to have_many(:triggers) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
63
    it { is_expected.to have_many(:pages_domains) }
64
    it { is_expected.to have_many(:labels).class_name('ProjectLabel').dependent(:destroy) }
ubudzisz's avatar
ubudzisz committed
65
    it { is_expected.to have_many(:users_star_projects).dependent(:destroy) }
66
67
    it { is_expected.to have_many(:environments).dependent(:destroy) }
    it { is_expected.to have_many(:deployments).dependent(:destroy) }
68
    it { is_expected.to have_many(:todos).dependent(:destroy) }
ubudzisz's avatar
ubudzisz committed
69
70
71
72
73
    it { is_expected.to have_many(:releases).dependent(:destroy) }
    it { is_expected.to have_many(:lfs_objects_projects).dependent(:destroy) }
    it { is_expected.to have_many(:project_group_links).dependent(:destroy) }
    it { is_expected.to have_many(:notification_settings).dependent(:destroy) }
    it { is_expected.to have_many(:forks).through(:forked_project_links) }
74

75
76
    context 'after initialized' do
      it "has a project_feature" do
77
        expect(Project.new.project_feature).to be_present
78
79
80
      end
    end

81
    describe '#members & #requesters' do
82
      let(:project) { create(:empty_project, :public, :access_requestable) }
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
      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
108
109
110
111
112

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

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

119
120
121
122
123
124
  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) }
125
    it { is_expected.to include_module(Gitlab::CurrentSettings) }
126
127
    it { is_expected.to include_module(Referable) }
    it { is_expected.to include_module(Sortable) }
128
129
  end

130
  describe 'validation' do
131
    let!(:project) { create(:empty_project) }
132

133
134
    it { is_expected.to validate_presence_of(:name) }
    it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) }
135
    it { is_expected.to validate_length_of(:name).is_at_most(255) }
136

137
138
    it { is_expected.to validate_presence_of(:path) }
    it { is_expected.to validate_uniqueness_of(:path).scoped_to(:namespace_id) }
139
140
141
142
    it { is_expected.to validate_length_of(:path).is_at_most(255) }

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

143
    it { is_expected.to validate_presence_of(:creator) }
144

145
    it { is_expected.to validate_presence_of(:namespace) }
146

147
    it { is_expected.to validate_presence_of(:repository_storage) }
148

149
    it 'does not allow new projects beyond user limits' do
150
      project2 = build(:empty_project)
151
152
      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
153
      expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/)
154
    end
155
156
157

    describe 'wiki path conflict' do
      context "when the new path has been used by the wiki of other Project" do
158
        it 'has an error on the name attribute' do
159
          new_project = build_stubbed(:empty_project, namespace_id: project.namespace_id, path: "#{project.path}.wiki")
160
161
162
163
164
165
166

          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
167
        it 'has an error on the name attribute' do
168
169
          project_with_wiki_suffix = create(:empty_project, path: 'foo.wiki')
          new_project = build_stubbed(:empty_project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo')
170
171
172
173
174
175

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

    context 'repository storages inclussion' do
178
      let(:project2) { build(:empty_project, repository_storage: 'missing') }
179
180
181
182
183
184

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

185
      it "does not allow repository storages that don't match a label in the configuration" do
186
187
188
189
        expect(project2).not_to be_valid
        expect(project2.errors[:repository_storage].first).to match(/is not included in the list/)
      end
    end
190

191
    it 'does not allow an invalid URI as import_url' do
192
      project2 = build(:empty_project, import_url: 'invalid://')
James Lopez's avatar
James Lopez committed
193
194
195
196

      expect(project2).not_to be_valid
    end

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

200
201
      expect(project2).to be_valid
    end
202

203
    it 'allows an empty URI' do
204
      project2 = build(:empty_project, import_url: '')
205

206
      expect(project2).to be_valid
207
208
209
    end

    it 'does not produce import data on an empty URI' do
210
      project2 = build(:empty_project, import_url: '')
211
212
213
214
215

      expect(project2.import_data).to be_nil
    end

    it 'does not produce import data on an invalid URI' do
216
      project2 = build(:empty_project, import_url: 'test://')
217
218
219

      expect(project2.import_data).to be_nil
    end
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239

    describe 'project pending deletion' do
      let!(:project_pending_deletion) do
        create(:empty_project,
               pending_delete: true)
      end
      let(:new_project) do
        build(:empty_project,
              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
gitlabhq's avatar
gitlabhq committed
240
  end
241

242
243
244
245
246
247
248
249
250
  describe 'default_scope' do
    it 'excludes projects pending deletion from the results' do
      project = create(:empty_project)
      create(:empty_project, pending_delete: true)

      expect(Project.all).to eq [project]
    end
  end

251
  describe 'project token' do
252
    it 'sets an random token if none provided' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
253
254
      project = FactoryGirl.create :empty_project, runners_token: ''
      expect(project.runners_token).not_to eq('')
255
256
    end

ubudzisz's avatar
ubudzisz committed
257
    it 'does not set an random token if one provided' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
258
259
      project = FactoryGirl.create :empty_project, runners_token: 'my-token'
      expect(project.runners_token).to eq('my-token')
260
261
    end
  end
gitlabhq's avatar
gitlabhq committed
262

263
  describe 'Respond to' do
264
265
266
267
268
    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) }
gitlabhq's avatar
gitlabhq committed
269
270
  end

271
272
273
274
275
276
277
  describe 'delegation' do
    it { is_expected.to delegate_method(:add_guest).to(:team) }
    it { is_expected.to delegate_method(:add_reporter).to(:team) }
    it { is_expected.to delegate_method(:add_developer).to(:team) }
    it { is_expected.to delegate_method(:add_master).to(:team) }
  end

278
  describe '#to_reference' do
279
    let(:owner)     { create(:user, name: 'Gitlab') }
280
    let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
281
282
    let(:project)   { create(:empty_project, path: 'sample-project', namespace: namespace) }
    let(:group)     { create(:group, name: 'Group', path: 'sample-group', owner: owner) }
283

284
    context 'when nil argument' do
285
286
287
288
289
      it 'returns nil' do
        expect(project.to_reference).to be_nil
      end
    end

290
    context 'when full is true' do
291
      it 'returns complete path to the project' do
292
293
294
        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'
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
      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
      let(:another_namespace_project) { create(:empty_project, name: 'another-project') }

      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
      let(:another_project) { create(:empty_project, namespace: namespace) }

315
      it 'returns path to the project' do
316
317
318
        expect(project.to_reference(another_project)).to eq 'sample-project'
      end
    end
319

320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
    context 'when different namespace / cross-project argument' do
      let(:another_namespace) { create(:namespace, path: 'another-namespace', owner: owner) }
      let(:another_project)   { create(:empty_project, path: 'another-project', namespace: another_namespace) }

      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
340
341
      end
    end
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
  end

  describe '#to_human_reference' do
    let(:owner) { create(:user, name: 'Gitlab') }
    let(:namespace) { create(:namespace, name: 'Sample namespace', owner: owner) }
    let(:project) { create(:empty_project, name: 'Sample project', namespace: namespace) }

    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
      let(:another_namespace_project) { create(:empty_project, name: 'another-project') }

      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
      let(:another_project) { create(:empty_project, namespace: namespace) }

      it 'returns name of the project' do
        expect(project.to_human_reference(another_project)).to eq 'Sample project'
      end
375
376
377
    end
  end

378
  describe '#repository_storage_path' do
379
    let(:project) { create(:empty_project, repository_storage: 'custom') }
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395

    before do
      FileUtils.mkdir('tmp/tests/custom_repositories')
      storages = { 'custom' => 'tmp/tests/custom_repositories' }
      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

396
  it 'returns valid url to repo' do
397
    project = Project.new(path: 'somewhere')
398
    expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
gitlabhq's avatar
gitlabhq committed
399
400
  end

Douwe Maan's avatar
Douwe Maan committed
401
402
403
404
405
406
  describe "#web_url" do
    let(:project) { create(:empty_project, path: "somewhere") }

    it 'returns the full web URL for this repo' do
      expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.path}/somewhere")
    end
407
408
  end

409
  describe "#new_issue_address" do
410
411
412
    let(:project) { create(:empty_project, path: "somewhere") }
    let(:user) { create(:user) }

413
414
415
416
417
418
    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
419
        address = "p+#{project.path_with_namespace}+#{user.incoming_email_token}@gl.ab"
420
421
422
423
424
425
426
427
428

        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
429

430
431
432
      it 'returns nil' do
        expect(project.new_issue_address(user)).to be_nil
      end
433
434
435
    end
  end

436
  describe 'last_activity methods' do
437
438
    let(:timestamp) { 2.hours.ago }
    # last_activity_at gets set to created_at upon creation
439
    let(:project) { create(:empty_project, created_at: timestamp, updated_at: timestamp) }
gitlabhq's avatar
gitlabhq committed
440

441
    describe 'last_activity' do
442
      it 'alias last_activity to last_event' do
443
444
        last_event = create(:event, project: project)

445
        expect(project.last_activity).to eq(last_event)
446
      end
gitlabhq's avatar
gitlabhq committed
447
448
    end

449
450
    describe 'last_activity_date' do
      it 'returns the creation date of the project\'s last event if present' do
451
452
        new_event = create(:event, project: project, created_at: Time.now)

453
        project.reload
454
        expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i)
455
      end
456

457
      it 'returns the project\'s last update date if it has no events' do
458
        expect(project.last_activity_date).to eq(project.updated_at)
459
      end
460
461
    end
  end
462

463
464
  describe '#get_issue' do
    let(:project) { create(:empty_project) }
Stan Hu's avatar
Stan Hu committed
465
    let!(:issue)  { create(:issue, project: project) }
466
467
468
469
470
    let(:user)    { create(:user) }

    before do
      project.team << [user, :developer]
    end
471
472
473

    context 'with default issues tracker' do
      it 'returns an issue' do
474
        expect(project.get_issue(issue.iid, user)).to eq issue
475
476
      end

Stan Hu's avatar
Stan Hu committed
477
478
479
480
      it 'returns count of open issues' do
        expect(project.open_issues_count).to eq(1)
      end

481
      it 'returns nil when no issue found' do
482
483
484
485
486
487
        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
488
489
490
491
492
493
494
495
496
      end
    end

    context 'with external issues tracker' do
      before do
        allow(project).to receive(:default_issues_tracker?).and_return(false)
      end

      it 'returns an ExternalIssue' do
497
        issue = project.get_issue('FOO-1234', user)
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
        expect(issue).to be_kind_of(ExternalIssue)
        expect(issue.iid).to eq 'FOO-1234'
        expect(issue.project).to eq project
      end
    end
  end

  describe '#issue_exists?' do
    let(:project) { create(:empty_project) }

    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

519
  describe '#to_param' do
520
521
522
    context 'with namespace' do
      before do
        @group = create :group, name: 'gitlab'
523
        @project = create(:empty_project, name: 'gitlabhq', namespace: @group)
524
525
      end

Vinnie Okada's avatar
Vinnie Okada committed
526
      it { expect(@project.to_param).to eq('gitlabhq') }
527
    end
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545

    context 'with invalid path' do
      it 'returns previous path to keep project suitable for use in URLs when persisted' do
        project = create(:empty_project, path: 'gitlab')
        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
        project = build(:empty_project, path: 'gitlab')
        project.path = 'foo&bar'

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

548
  describe '#repository' do
549
    let(:project) { create(:project, :repository) }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
550

551
    it 'returns valid repo' do
552
      expect(project.repository).to be_kind_of(Repository)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
553
554
    end
  end
555

556
  describe '#default_issues_tracker?' do
557
    it "is true if used internal tracker" do
558
559
      project = build(:empty_project)

560
      expect(project.default_issues_tracker?).to be_truthy
561
562
    end

563
    it "is false if used other tracker" do
564
565
566
567
      # NOTE: The current nature of this factory requires persistence
      project = create(:redmine_project)

      expect(project.default_issues_tracker?).to be_falsey
568
569
570
    end
  end

571
  describe '#external_issue_tracker' do
572
    let(:project) { create(:empty_project) }
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
    let(:ext_project) { create(:redmine_project) }

    context 'on existing projects with no value for has_external_issue_tracker' do
      before(:each) do
        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

606
  describe '#cache_has_external_issue_tracker' do
607
    let(:project) { create(:empty_project, has_external_issue_tracker: nil) }
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627

    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

628
  describe '#has_wiki?' do
629
    let(:no_wiki_project)       { create(:empty_project, :wiki_disabled, has_external_wiki: false) }
630
631
    let(:wiki_enabled_project)  { create(:empty_project) }
    let(:external_wiki_project) { create(:empty_project, has_external_wiki: true) }
632
633
634
635
636
637
638
639

    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

640
  describe '#external_wiki' do
641
    let(:project) { create(:empty_project) }
642

643
644
645
646
647
    context 'with an active external wiki' do
      before do
        create(:service, project: project, type: 'ExternalWikiService', active: true)
        project.external_wiki
      end
648

649
650
651
      it 'sets :has_external_wiki as true' do
        expect(project.has_external_wiki).to be(true)
      end
652

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

656
657
658
659
        project.services.external_wikis.first.destroy

        expect(project.has_external_wiki).to be(false)
      end
660
661
    end

662
663
664
665
    context 'with an inactive external wiki' do
      before do
        create(:service, project: project, type: 'ExternalWikiService', active: false)
      end
666

667
668
669
      it 'sets :has_external_wiki as false' do
        expect(project.has_external_wiki).to be(false)
      end
670
671
    end

672
673
674
675
    context 'with no external wiki' do
      before do
        project.external_wiki
      end
676

677
678
679
680
681
682
683
684
685
686
687
      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
688
689
690
    end
  end

691
  describe '#open_branches' do
692
    let(:project) { create(:project, :repository) }
693
694
695
696
697

    before do
      project.protected_branches.create(name: 'master')
    end

698
699
    it { expect(project.open_branches.map(&:name)).to include('feature') }
    it { expect(project.open_branches.map(&:name)).not_to include('master') }
700

701
    it "includes branches matching a protected branch wildcard" do
702
703
704
705
      expect(project.open_branches.map(&:name)).to include('feature')

      create(:protected_branch, name: 'feat*', project: project)

706
      expect(Project.find(project.id).open_branches.map(&:name)).to include('feature')
707
    end
708
  end
Ciro Santilli's avatar
Ciro Santilli committed
709

710
711
  describe '#star_count' do
    it 'counts stars from multiple users' do
Ciro Santilli's avatar
Ciro Santilli committed
712
713
      user1 = create :user
      user2 = create :user
714
      project = create(:empty_project, :public)
Ciro Santilli's avatar
Ciro Santilli committed
715
716

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

Ciro Santilli's avatar
Ciro Santilli committed
718
      user1.toggle_star(project)
719
720
      expect(project.reload.star_count).to eq(1)

Ciro Santilli's avatar
Ciro Santilli committed
721
      user2.toggle_star(project)
722
723
724
      project.reload
      expect(project.reload.star_count).to eq(2)

Ciro Santilli's avatar
Ciro Santilli committed
725
      user1.toggle_star(project)
726
727
728
      project.reload
      expect(project.reload.star_count).to eq(1)

Ciro Santilli's avatar
Ciro Santilli committed
729
      user2.toggle_star(project)
730
731
732
733
      project.reload
      expect(project.reload.star_count).to eq(0)
    end

734
    it 'counts stars on the right project' do
735
      user = create :user
736
737
      project1 = create(:empty_project, :public)
      project2 = create(:empty_project, :public)
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764

      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
765
766
    end
  end
767

768
  describe '#avatar_type' do
769
    let(:project) { create(:empty_project) }
770

771
    it 'is true if avatar is image' do
772
      project.update_attribute(:avatar, 'uploads/avatar.png')
773
      expect(project.avatar_type).to be_truthy
774
775
    end

776
    it 'is false if avatar is html page' do
777
      project.update_attribute(:avatar, 'uploads/avatar.html')
778
      expect(project.avatar_type).to eq(['only images allowed'])
779
780
    end
  end
sue445's avatar
sue445 committed
781

782
  describe '#avatar_url' do
sue445's avatar
sue445 committed
783
784
    subject { project.avatar_url }

785
    let(:project) { create(:empty_project) }
sue445's avatar
sue445 committed
786
787
788
789
790
791
792
793
794
795
796

    context 'When avatar file is uploaded' do
      before do
        project.update_columns(avatar: 'uploads/avatar.png')
        allow(project.avatar).to receive(:present?) { true }
      end

      let(:avatar_path) do
        "/uploads/project/avatar/#{project.id}/uploads/avatar.png"
      end

797
      it { should eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
sue445's avatar
sue445 committed
798
799
800
801
802
803
804
805
806
807
808
    end

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

      let(:avatar_path) do
        "/#{project.namespace.name}/#{project.path}/avatar"
      end

809
      it { should eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
sue445's avatar
sue445 committed
810
    end
811
812
813
814
815
816

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

      it { should eq nil }
    end
sue445's avatar
sue445 committed
817
  end
818

819
  describe '#pipeline_for' do
820
    let(:project) { create(:project, :repository) }
821
    let!(:pipeline) { create_pipeline }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
822

823
824
    shared_examples 'giving the correct pipeline' do
      it { is_expected.to eq(pipeline) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
825

826
827
      context 'return latest' do
        let!(:pipeline2) { create_pipeline }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
828

829
        it { is_expected.to eq(pipeline2) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
830
      end
831
832
833
834
835
836
837
838
839
840
841
842
843
    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
844

845
846
847
848
849
    def create_pipeline
      create(:ci_pipeline,
             project: project,
             ref: 'master',
             sha: project.commit('master').sha)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
850
    end
851
  end
852

853
  describe '#builds_enabled' do
854
    let(:project) { create(:empty_project) }
855

856
857
858
    subject { project.builds_enabled }

    it { expect(project.builds_enabled?).to be_truthy }
859
  end
860

861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
  describe '.with_shared_runners' do
    subject { Project.with_shared_runners }

    context 'when shared runners are enabled for project' do
      let!(:project) { create(:empty_project, shared_runners_enabled: true) }

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

    context 'when shared runners are disabled for project' do
      let!(:project) { create(:empty_project, shared_runners_enabled: false) }

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

881
882
883
884
885
886
887
888
889
890
891
892
893
894
  describe '.cached_count', caching: true do
    let(:group)     { create(:group, :public) }
    let!(:project1) { create(:empty_project, :public, group: group) }
    let!(:project2) { create(:empty_project, :public, group: group) }

    it 'returns total project count' do
      expect(Project).to receive(:count).once.and_call_original

      3.times do
        expect(Project.cached_count).to eq(2)
      end
    end
  end

895
  describe '.trending' do
Felipe Artur's avatar
Felipe Artur committed
896
    let(:group)    { create(:group, :public) }
897
898
899
900
901
902
903
904
905
906
    let(:project1) { create(:empty_project, :public, group: group) }
    let(:project2) { create(:empty_project, :public, group: group) }

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

      create(:note_on_commit, project: project2)

907
      TrendingProject.refresh!
908
909
    end

910
    subject { described_class.trending.to_a }
911

912
913
    it 'sorts projects by the amount of notes in descending order' do
      expect(subject).to eq([project1, project2])
914
    end
915
916
917
918
919
920
921
922

    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
923
  end
Yorick Peterse's avatar
Yorick Peterse committed
924
925

  describe '.visible_to_user' do
926
    let!(:project) { create(:empty_project, :private) }
Yorick Peterse's avatar
Yorick Peterse committed
927
928
929
930
931
932
    let!(:user)    { create(:user) }

    subject { described_class.visible_to_user(user) }

    describe 'when a user has access to a project' do
      before do
933
        project.add_user(user, Gitlab::Access::MASTER)
Yorick Peterse's avatar
Yorick Peterse committed
934
935
936
937
938
939
940
941
942
      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
943

944
945
946
947
  context 'repository storage by default' do
    let(:project) { create(:empty_project) }

    before do
948
949
950
951
      storages = {
        'default' => 'tmp/tests/repositories',
        'picked'  => 'tmp/tests/repositories',
      }
952
953
954
      allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
    end

955
956
957
958
959
    it 'picks storage from ApplicationSetting' do
      expect_any_instance_of(ApplicationSetting).to receive(:pick_repository_storage).and_return('picked')

      expect(project.repository_storage).to eq('picked')
    end
960
961
  end

962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
  context 'shared runners by default' do
    let(:project) { create(:empty_project) }

    subject { project.shared_runners_enabled }

    context 'are enabled' do
      before { stub_application_setting(shared_runners_enabled: true) }

      it { is_expected.to be_truthy }
    end

    context 'are disabled' do
      before { stub_application_setting(shared_runners_enabled: false) }

      it { is_expected.to be_falsey }
    end
  end

980
  describe '#any_runners' do
981
    let(:project) { create(:empty_project, shared_runners_enabled: shared_runners_enabled) }
982
983
    let(:specific_runner) { create(:ci_runner) }
    let(:shared_runner) { create(:ci_runner, :shared) }
984
985
986

    context 'for shared runners disabled' do
      let(:shared_runners_enabled) { false }
987

988
      it 'has no runners available' do
989
990
        expect(project.any_runners?).to be_falsey
      end
991

992
      it 'has a specific runner' do
993
        project.runners << specific_runner
994
995
        expect(project.any_runners?).to be_truthy
      end
996

997
      it 'has a shared runner, but they are prohibited to use' do
998
999
1000
        shared_runner
        expect(project.any_runners?).to be_falsey
      end
1001

1002
      it 'checks the presence of specific runner' do
1003
        project.runners << specific_runner
1004
1005
1006
        expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
      end
    end
1007

1008
1009
    context 'for shared runners enabled' do
      let(:shared_runners_enabled) { true }
1010

1011
      it 'has a shared runner' do
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
        shared_runner
        expect(project.any_runners?).to be_truthy
      end

      it 'checks the presence of shared runner' do
        shared_runner
        expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
      end
    end
  end
1022

1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
  describe '#shared_runners' do
    let!(:runner) { create(:ci_runner, :shared) }

    subject { project.shared_runners }

    context 'when shared runners are enabled for project' do
      let!(:project) { create(:empty_project, shared_runners_enabled: true) }

      it "returns a list of shared runners" do
        is_expected.to eq([runner])
      end
    end

    context 'when shared runners are disabled for project' do
      let!(:project) { create(:empty_project, shared_runners_enabled: false) }

      it "returns a empty list" do
        is_expected.to be_empty
      end
    end
  end

1045
  describe '#visibility_level_allowed?' do
1046
    let(:project) { create(:empty_project, :internal) }
1047
1048
1049
1050
1051
1052
1053
1054

    context 'when checking on non-forked project' do
      it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
      it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
      it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_truthy }
    end

    context 'when checking on forked project' do
1055
1056
      let(:project)        { create(:empty_project, :internal) }
      let(:forked_project) { create(:empty_project, forked_from_project: project) }
1057
1058
1059
1060
1061

      it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
      it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
      it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey }
    end
1062
  end
1063

1064
1065
1066
1067
1068
1069
  describe '#pages_deployed?' do
    let(:project) { create :empty_project }

    subject { project.pages_deployed? }

    context 'if public folder does exist' do
1070
      before { allow(Dir).to receive(:exist?).with(project.public_pages_path).and_return(true) }
1071
1072
1073
1074
1075
1076
1077
1078
1079

      it { is_expected.to be_truthy }
    end

    context "if public folder doesn't exist" do
      it { is_expected.to be_falsey }
    end
  end

1080
  describe '.search' do
1081
    let(:project) { create(:empty_project, description: 'kitten mittens') }
1082

1083
1084
1085
    it 'returns projects with a matching name' do
      expect(described_class.search(project.name)).to eq([project])
    end
1086

1087
1088
1089
    it 'returns projects with a partially matching name' do
      expect(described_class.search(project.name[0..2])).to eq([project])
    end
1090

1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
    it 'returns projects with a matching name regardless of the casing' do
      expect(described_class.search(project.name.upcase)).to eq([project])
    end

    it 'returns projects with a matching description' do
      expect(described_class.search(project.description)).to eq([project])
    end

    it 'returns projects with a partially matching description' do
      expect(described_class.search('kitten')).to eq([project])
    end

    it 'returns projects with a matching description regardless of the casing' do
      expect(described_class.search('KITTEN')).to eq([project])
    end

    it 'returns projects with a matching path' do
      expect(described_class.search(project.path)).to eq([project])
    end

    it 'returns projects with a partially matching path' do
      expect(described_class.search(project.path[0..2])).to eq([project])
    end

    it 'returns projects with a matching path regardless of the casing' do
      expect(described_class.search(project.path.upcase)).to eq([project])
    end

    it 'returns projects with a matching namespace name' do
      expect(described_class.search(project.namespace.name)).to eq([project])
    end

    it 'returns projects with a partially matching namespace name' do
      expect(described_class.search(project.namespace.name[0..2])).to eq([project])
    end

    it 'returns projects with a matching namespace name regardless of the casing' do
      expect(described_class.search(project.namespace.name.upcase)).to eq([project])
    end
1130
1131
1132
1133
1134

    it 'returns projects when eager loading namespaces' do
      relation = described_class.all.includes(:namespace)

      expect(relation.search(project.namespace.name)).to eq([project])
1135
    end
1136
  end
1137
1138

  describe '#rename_repo' do
1139
    let(:project) { create(:project, :repository) }
1140
1141
1142
1143
1144
1145
1146
1147
    let(:gitlab_shell) { Gitlab::Shell.new }

    before do
      # Project#gitlab_shell returns a new instance of Gitlab::Shell on every
      # call. This makes testing a bit easier.
      allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)

      allow(project).to receive(:previous_changes).and_return('path' => ['foo'])
Kamil Trzcinski's avatar
Kamil Trzcinski committed
1148
    end
1149

Kamil Trzcinski's avatar
Kamil Trzcinski committed
1150
    it 'renames a repository' do
1151
1152
1153
1154
      ns = project.namespace_dir

      expect(gitlab_shell).to receive(:mv_repository).
        ordered.
1155
        with(project.repository_storage_path, "#{ns}/foo", "#{ns}/#{project.path}").
1156
1157
1158
1159
        and_return(true)

      expect(gitlab_shell).to receive(:mv_repository).
        ordered.
1160
        with(project.repository_storage_path, "#{ns}/foo.wiki", "#{ns}/#{project.path}.wiki").
1161
1162
1163
1164
1165
1166
1167
<