Read about our upcoming Code of Conduct on this issue

project_spec.rb 91.5 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
    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) }
Matt Coleman's avatar
Matt Coleman committed
27
    it { is_expected.to have_one(:packagist_service) }
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
54
    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
55
56
    it { is_expected.to have_one(:last_event).class_name('Event') }
    it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) }
Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
57
    it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
58
    it { is_expected.to have_many(:commit_statuses) }
59
    it { is_expected.to have_many(:pipelines) }
60
    it { is_expected.to have_many(:builds) }
61
    it { is_expected.to have_many(:build_trace_section_names)}
62
63
    it { is_expected.to have_many(:runner_projects) }
    it { is_expected.to have_many(:runners) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
64
    it { is_expected.to have_many(:active_runners) }
65
66
    it { is_expected.to have_many(:variables) }
    it { is_expected.to have_many(:triggers) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
67
    it { is_expected.to have_many(:pages_domains) }
68
69
70
71
72
73
74
75
76
    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
77
    it { is_expected.to have_many(:forks).through(:forked_project_links) }
78
    it { is_expected.to have_many(:uploads).dependent(:destroy) }
79
    it { is_expected.to have_many(:pipeline_schedules) }
80
    it { is_expected.to have_many(:members_and_requesters) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
81
    it { is_expected.to have_one(:cluster) }
82

83
84
    context 'after initialized' do
      it "has a project_feature" do
85
        expect(described_class.new.project_feature).to be_present
86
87
88
      end
    end

89
    describe '#members & #requesters' do
90
      let(:project) { create(:project, :public, :access_requestable) }
91
92
93
94
95
96
97
      let(:requester) { create(:user) }
      let(:developer) { create(:user) }
      before do
        project.request_access(requester)
        project.team << [developer, :developer]
      end

98
99
      it_behaves_like 'members and requesters associations' do
        let(:namespace) { project }
100
101
      end
    end
102
103
104
105
106

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

107
        expect { subject.boards.build }.to raise_error(Project::BoardLimitExceeded, 'Number of permitted boards exceeded')
108
109
110
        expect(subject.boards.size).to eq 1
      end
    end
gitlabhq's avatar
gitlabhq committed
111
112
  end

113
114
115
116
117
118
  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) }
119
    it { is_expected.to include_module(Gitlab::CurrentSettings) }
120
121
    it { is_expected.to include_module(Referable) }
    it { is_expected.to include_module(Sortable) }
122
123
  end

124
  describe 'validation' do
125
    let!(:project) { create(:project) }
126

127
128
    it { is_expected.to validate_presence_of(:name) }
    it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) }
129
    it { is_expected.to validate_length_of(:name).is_at_most(255) }
130

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

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

137
138
139
    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) }
140

141
    it { is_expected.to validate_presence_of(:creator) }
142

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

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

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

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

          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
165
        it 'has an error on the name attribute' do
166
167
          project_with_wiki_suffix = create(:project, path: 'foo.wiki')
          new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo')
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
    end
174

175
    context 'repository storages inclusion' do
176
      let(:project2) { build(:project, repository_storage: 'missing') }
177
178

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

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

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

      expect(project2).not_to be_valid
    end

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

198
199
      expect(project2).to be_valid
    end
200

201
    it 'allows an empty URI' do
202
      project2 = build(:project, import_url: '')
203

204
      expect(project2).to be_valid
205
206
207
    end

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

      expect(project2.import_data).to be_nil
    end

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

      expect(project2.import_data).to be_nil
    end
218

219
    it "does not allow blocked import_url localhost" do
220
      project2 = build(:project, import_url: 'http://localhost:9000/t.git')
221
222
223
224
225
226

      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
227
      project2 = build(:project, import_url: 'http://github.com:25/t.git')
228
229
230
231
232

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

233
234
    describe 'project pending deletion' do
      let!(:project_pending_deletion) do
235
        create(:project,
236
237
238
               pending_delete: true)
      end
      let(:new_project) do
239
        build(:project,
240
241
242
243
244
245
246
247
248
249
250
251
              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
252
253
254

    describe 'path validation' do
      it 'allows paths reserved on the root namespace' do
255
        project = build(:project, path: 'api')
256
257
258
259
260

        expect(project).to be_valid
      end

      it 'rejects paths reserved on another level' do
261
        project = build(:project, path: 'tree')
262
263
264

        expect(project).not_to be_valid
      end
265
266
267

      it 'rejects nested paths' do
        parent = create(:group, :nested, path: 'environments')
268
        project = build(:project, path: 'folders', namespace: parent)
269
270
271

        expect(project).not_to be_valid
      end
272
273
274

      it 'allows a reserved group name' do
        parent = create(:group)
275
        project = build(:project, path: 'avatar', namespace: parent)
276
277
278

        expect(project).to be_valid
      end
279
280
281
282
283
284

      it 'allows a path ending in a period' do
        project = build(:project, path: 'foo.')

        expect(project).to be_valid
      end
285
    end
gitlabhq's avatar
gitlabhq committed
286
  end
287

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

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

300
  describe 'Respond to' do
301
302
303
304
305
    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) }
306
    it { is_expected.to respond_to(:full_path) }
gitlabhq's avatar
gitlabhq committed
307
308
  end

309
  describe 'delegation' do
310
311
312
313
314
315
316
317
    [: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) }
318
319
  end

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

326
    context 'when nil argument' do
327
328
329
330
331
      it 'returns nil' do
        expect(project.to_reference).to be_nil
      end
    end

332
    context 'when full is true' do
333
      it 'returns complete path to the project' do
334
335
336
        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'
337
338
339
340
341
342
343
344
345
346
      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
347
      let(:another_namespace_project) { create(:project, name: 'another-project') }
348
349
350
351
352
353
354

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

357
      it 'returns path to the project' do
358
359
360
        expect(project.to_reference(another_project)).to eq 'sample-project'
      end
    end
361

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

      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
382
383
      end
    end
384
385
386
387
388
  end

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

    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
404
      let(:another_namespace_project) { create(:project, name: 'another-project') }
405
406
407
408
409
410
411

      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
412
      let(:another_project) { create(:project, namespace: namespace) }
413
414
415
416

      it 'returns name of the project' do
        expect(project.to_human_reference(another_project)).to eq 'Sample project'
      end
417
418
419
    end
  end

420
421
422
423
424
425
426
427
428
429
430
431
  describe '#merge_method' do
    it 'returns "ff" merge_method when ff is enabled' do
      project = build(:project, merge_requests_ff_only_enabled: true)
      expect(project.merge_method).to be :ff
    end

    it 'returns "merge" merge_method when ff is disabled' do
      project = build(:project, merge_requests_ff_only_enabled: false)
      expect(project.merge_method).to be :merge
    end
  end

432
  describe '#repository_storage_path' do
433
    let(:project) { create(:project) }
434
435

    it 'returns the repository storage path' do
436
      expect(Dir.exist?(project.repository_storage_path)).to be(true)
437
438
439
    end
  end

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

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

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

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

457
458
459
460
461
462
    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
463
        address = "p+#{project.full_path}+#{user.incoming_email_token}@gl.ab"
464
465
466
467
468
469
470
471
472

        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
473

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

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

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

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

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

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

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

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

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

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

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

525
      it 'returns nil when no issue found' do
526
527
528
529
530
531
        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
532
533
534
535
      end
    end

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

541
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
      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
578
579
580
581
582
      end
    end
  end

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

    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

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

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

    context 'with invalid path' do
      it 'returns previous path to keep project suitable for use in URLs when persisted' do
608
        project = create(:project, path: 'gitlab')
609
610
611
612
613
614
615
        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
616
        project = build(:project, path: 'gitlab')
617
618
619
620
621
622
        project.path = 'foo&bar'

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

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

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

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

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

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

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

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

    context 'on existing projects with no value for has_external_issue_tracker' do
653
      before do
654
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
        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

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

    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
Toon Claes's avatar
Toon Claes committed
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740

    it 'does not cache data when in a read-only GitLab instance' do
      allow(Gitlab::Database).to receive(:read_only?) { true }

      expect do
        project.cache_has_external_issue_tracker
      end.not_to change { project.has_external_issue_tracker }
    end
  end

  describe '#cache_has_external_wiki' do
    let(:project) { create(:project, has_external_wiki: nil) }

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

      expect do
        project.cache_has_external_wiki
      end.to change { project.has_external_wiki}.to(true)
    end

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

      expect do
        project.cache_has_external_wiki
      end.to change { project.has_external_wiki}.to(false)
    end

    it 'does not cache data when in a read-only GitLab instance' do
      allow(Gitlab::Database).to receive(:read_only?) { true }

      expect do
        project.cache_has_external_wiki
      end.not_to change { project.has_external_wiki }
    end
741
742
  end

743
  describe '#has_wiki?' do
744
745
746
    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) }
747
748
749
750
751
752
753
754

    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

755
  describe '#external_wiki' do
756
    let(:project) { create(:project) }
757

758
759
760
761
762
    context 'with an active external wiki' do
      before do
        create(:service, project: project, type: 'ExternalWikiService', active: true)
        project.external_wiki
      end
763

764
765
766
      it 'sets :has_external_wiki as true' do
        expect(project.has_external_wiki).to be(true)
      end
767

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

771
772
773
774
        project.services.external_wikis.first.destroy

        expect(project.has_external_wiki).to be(false)
      end
775
776
    end

777
778
779
780
    context 'with an inactive external wiki' do
      before do
        create(:service, project: project, type: 'ExternalWikiService', active: false)
      end
781

782
783
784
      it 'sets :has_external_wiki as false' do
        expect(project.has_external_wiki).to be(false)
      end
785
786
    end

787
788
789
790
    context 'with no external wiki' do
      before do
        project.external_wiki
      end
791

792
793
794
795
796
797
798
799
800
801
802
      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
803
804
805
    end
  end

806
807
  describe '#star_count' do
    it 'counts stars from multiple users' do
Ciro Santilli's avatar
Ciro Santilli committed
808
809
      user1 = create :user
      user2 = create :user
810
      project = create(:project, :public)
Ciro Santilli's avatar
Ciro Santilli committed
811
812

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

Ciro Santilli's avatar
Ciro Santilli committed
814
      user1.toggle_star(project)
815
816
      expect(project.reload.star_count).to eq(1)

Ciro Santilli's avatar
Ciro Santilli committed
817
      user2.toggle_star(project)
818
819
820
      project.reload
      expect(project.reload.star_count).to eq(2)

Ciro Santilli's avatar
Ciro Santilli committed
821
      user1.toggle_star(project)
822
823
824
      project.reload
      expect(project.reload.star_count).to eq(1)

Ciro Santilli's avatar
Ciro Santilli committed
825
      user2.toggle_star(project)
826
827
828
829
      project.reload
      expect(project.reload.star_count).to eq(0)
    end

830
    it 'counts stars on the right project' do
831
      user = create :user
832
833
      project1 = create(:project, :public)
      project2 = create(:project, :public)
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860

      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
861
862
    end
  end
863

864
  describe '#avatar_type' do
865
    let(:project) { create(:project) }
866

867
    it 'is true if avatar is image' do
868
      project.update_attribute(:avatar, 'uploads/avatar.png')
869
      expect(project.avatar_type).to be_truthy
870
871
    end

872
    it 'is false if avatar is html page' do
873
      project.update_attribute(:avatar, 'uploads/avatar.html')
874
      expect(project.avatar_type).to eq(['only images allowed'])
875
876
    end
  end
sue445's avatar
sue445 committed
877

878
  describe '#avatar_url' do
sue445's avatar
sue445 committed
879
880
    subject { project.avatar_url }

881
    let(:project) { create(:project) }
sue445's avatar
sue445 committed
882

883
    context 'when avatar file is uploaded' do
884
      let(:project) { create(:project, :public, :with_avatar) }
885
      let(:avatar_path) { "/uploads/-/system/project/avatar/#{project.id}/dk.png" }
886
      let(:gitlab_host) { "http://#{Gitlab.config.gitlab.host}" }
sue445's avatar
sue445 committed
887

888
889
890
891
892
893
894
895
      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
896
897
898
899
900
901
902
    end

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

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

905
      it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
sue445's avatar
sue445 committed
906
    end
907
908

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

911
      it { is_expected.to eq nil }
912
    end
sue445's avatar
sue445 committed
913
  end
914

915
  describe '#pipeline_for' do
916
    let(:project) { create(:project, :repository) }
917
    let!(:pipeline) { create_pipeline }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
918

919
920
    shared_examples 'giving the correct pipeline' do
      it { is_expected.to eq(pipeline) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
921

922
923
      context 'return latest' do
        let!(:pipeline2) { create_pipeline }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
924

925
        it { is_expected.to eq(pipeline2) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
926
      end
927
928
929
930
931
932
933
934
935
936
937
938
939
    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
940

941
942
943
944
945
    def create_pipeline
      create(:ci_pipeline,
             project: project,
             ref: 'master',
             sha: project.commit('master').sha)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
946
    end
947
  end
948

949
  describe '#builds_enabled' do
950
    let(:project) { create(:project) }
951

952
953
954
    subject { project.builds_enabled }

    it { expect(project.builds_enabled?).to be_truthy }
955
  end
956

957
  describe '.with_shared_runners' do
958
    subject { described_class.with_shared_runners }
959
960

    context 'when shared runners are enabled for project' do
961
      let!(:project) { create(:project, shared_runners_enabled: true) }
962
963
964
965
966
967
968

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

    context 'when shared runners are disabled for project' do
969
      let!(:project) { create(:project, shared_runners_enabled: false) }
970
971
972
973
974
975
976

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

977
  describe '.cached_count', :use_clean_rails_memory_store_caching do
978
    let(:group)     { create(:group, :public) }
979
980
    let!(:project1) { create(:project, :public, group: group) }
    let!(:project2) { create(:project, :public, group: group) }
981
982

    it 'returns total project count' do
983
      expect(described_class).to receive(:count).once.and_call_original
984
985

      3.times do
986
        expect(described_class.cached_count).to eq(2)
987
988
989
990
      end
    end
  end

991
  describe '.trending' do
Felipe Artur's avatar
Felipe Artur committed
992
    let(:group)    { create(:group, :public) }
993
994
    let(:project1) { create(:project, :public, group: group) }
    let(:project2) { create(:project, :public, group: group) }
995
996
997
998
999
1000
1001
1002

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

      create(:note_on_commit, project: project2)

1003
      TrendingProject.refresh!
1004
1005
    end

1006
    subject { described_class.trending.to_a }
1007

1008
1009
    it 'sorts projects by the amount of notes in descending order' do
      expect(subject).to eq([project1, project2])
1010
    end
1011
1012
1013
1014
1015
1016
1017
1018

    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
1019
  end
Yorick Peterse's avatar
Yorick Peterse committed
1020

Toon Claes's avatar
Toon Claes committed
1021
1022
1023
1024
  describe '.starred_by' do
    it 'returns only projects starred by the given user' do
      user1 = create(:user)
      user2 = create(:user)
1025
1026
1027
      project1 = create(:project)
      project2 = create(:project)
      create(:project)
Toon Claes's avatar
Toon Claes committed
1028
1029
1030
      user1.toggle_star(project1)
      user2.toggle_star(project2)

1031
      expect(described_class.starred_by(user1)).to contain_exactly(project1)
Toon Claes's avatar
Toon Claes committed
1032
1033
1034
    end
  end

Yorick Peterse's avatar
Yorick Peterse committed
1035
  describe '.visible_to_user' do
1036
    let!(:project) { create(:project, :private) }
Yorick Peterse's avatar
Yorick Peterse committed
1037
1038
1039
1040
1041
1042
    let!(:user)    { create(:user) }

    subject { described_class.visible_to_user(user) }

    describe 'when a user has access to a project' do
      before do
1043
        project.add_user(user, Gitlab::Access::MASTER)
Yorick Peterse's avatar
Yorick Peterse committed
1044
1045
1046
1047
1048
1049
1050
1051
1052
      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
1053