Read about our upcoming Code of Conduct on this issue

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

3
describe Project do
4
5
  include ProjectForksHelper

6
  describe 'associations' do
7
8
9
10
    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
11
    it { is_expected.to have_many(:services) }
12
13
14
15
16
    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) }
17
    it { is_expected.to have_many(:users).through(:project_members) }
18
19
20
21
    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) }
22
    it { is_expected.to have_many(:deploy_keys) }
23
24
25
26
27
28
    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
29
    it { is_expected.to have_one(:packagist_service) }
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
55
56
    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
57
58
    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
59
    it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
60
    it { is_expected.to have_many(:commit_statuses) }
61
    it { is_expected.to have_many(:pipelines) }
62
    it { is_expected.to have_many(:builds) }
63
    it { is_expected.to have_many(:build_trace_section_names)}
64
65
66
67
    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
68
    it { is_expected.to have_many(:pages_domains) }
69
70
71
72
73
74
75
76
77
    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
78
    it { is_expected.to have_many(:forks).through(:forked_project_links) }
Jan Provaznik's avatar
Jan Provaznik committed
79
    it { is_expected.to have_many(:uploads) }
80
    it { is_expected.to have_many(:pipeline_schedules) }
81
    it { is_expected.to have_many(:members_and_requesters) }
82
    it { is_expected.to have_many(:clusters) }
83
    it { is_expected.to have_many(:custom_attributes).class_name('ProjectCustomAttribute') }
84
    it { is_expected.to have_many(:project_badges).class_name('ProjectBadge') }
85
    it { is_expected.to have_many(:lfs_file_locks) }
Mayra Cabrera's avatar
Mayra Cabrera committed
86
87
    it { is_expected.to have_many(:project_deploy_tokens) }
    it { is_expected.to have_many(:deploy_tokens).through(:project_deploy_tokens) }
88

89
90
    context 'after initialized' do
      it "has a project_feature" do
91
        expect(described_class.new.project_feature).to be_present
92
93
94
      end
    end

95
96
97
98
99
100
101
102
103
    context 'when creating a new project' do
      it 'automatically creates a CI/CD settings row' do
        project = create(:project)

        expect(project.ci_cd_settings).to be_an_instance_of(ProjectCiCdSetting)
        expect(project.ci_cd_settings).to be_persisted
      end
    end

104
105
106
107
    context 'updating cd_cd_settings' do
      it 'does not raise an error' do
        project = create(:project)

James Lopez's avatar
James Lopez committed
108
        expect { project.update(ci_cd_settings: nil) }.not_to raise_exception
109
110
111
      end
    end

112
    describe '#members & #requesters' do
113
      let(:project) { create(:project, :public, :access_requestable) }
114
115
116
117
      let(:requester) { create(:user) }
      let(:developer) { create(:user) }
      before do
        project.request_access(requester)
118
        project.add_developer(developer)
119
120
      end

121
122
      it_behaves_like 'members and requesters associations' do
        let(:namespace) { project }
123
124
      end
    end
125
126
127
128
129

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

130
        expect { subject.boards.build }.to raise_error(Project::BoardLimitExceeded, 'Number of permitted boards exceeded')
131
132
133
        expect(subject.boards.size).to eq 1
      end
    end
gitlabhq's avatar
gitlabhq committed
134
135
  end

136
137
138
139
140
141
142
143
  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) }
    it { is_expected.to include_module(Referable) }
    it { is_expected.to include_module(Sortable) }
144
145
  end

146
  describe 'validation' do
147
    let!(:project) { create(:project) }
148

149
150
    it { is_expected.to validate_presence_of(:name) }
    it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) }
151
    it { is_expected.to validate_length_of(:name).is_at_most(255) }
152

153
    it { is_expected.to validate_presence_of(:path) }
154
155
156
157
    it { is_expected.to validate_length_of(:path).is_at_most(255) }

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

158
159
160
    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) }
161
    it { is_expected.not_to allow_value('/test/foo').for(:ci_config_path) }
162

163
    it { is_expected.to validate_presence_of(:creator) }
164

165
    it { is_expected.to validate_presence_of(:namespace) }
166

167
    it { is_expected.to validate_presence_of(:repository_storage) }
168

169
    it 'does not allow new projects beyond user limits' do
170
      project2 = build(:project)
171
172
      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
173
      expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/)
174
    end
175
176
177

    describe 'wiki path conflict' do
      context "when the new path has been used by the wiki of other Project" do
178
        it 'has an error on the name attribute' do
179
          new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki")
180
181
182
183
184
185
186

          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
187
        it 'has an error on the name attribute' do
188
189
          project_with_wiki_suffix = create(:project, path: 'foo.wiki')
          new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo')
190
191
192
193
194
195

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

197
    context 'repository storages inclusion' do
198
      let(:project2) { build(:project, repository_storage: 'missing') }
199
200

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

205
      it "does not allow repository storages that don't match a label in the configuration" do
206
207
208
209
        expect(project2).not_to be_valid
        expect(project2.errors[:repository_storage].first).to match(/is not included in the list/)
      end
    end
210

211
    it 'does not allow an invalid URI as import_url' do
212
      project2 = build(:project, import_url: 'invalid://')
James Lopez's avatar
James Lopez committed
213
214
215
216

      expect(project2).not_to be_valid
    end

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

220
221
      expect(project2).to be_valid
    end
222

223
    it 'allows an empty URI' do
224
      project2 = build(:project, import_url: '')
225

226
      expect(project2).to be_valid
227
228
229
    end

    it 'does not produce import data on an empty URI' do
230
      project2 = build(:project, import_url: '')
231
232
233
234
235

      expect(project2.import_data).to be_nil
    end

    it 'does not produce import data on an invalid URI' do
236
      project2 = build(:project, import_url: 'test://')
237
238
239

      expect(project2.import_data).to be_nil
    end
240

241
    it "does not allow import_url pointing to localhost" do
242
      project2 = build(:project, import_url: 'http://localhost:9000/t.git')
243
244

      expect(project2).to be_invalid
245
      expect(project2.errors[:import_url].first).to include('Requests to localhost are not allowed')
246
247
    end

248
    it "does not allow import_url with invalid ports" do
249
      project2 = build(:project, import_url: 'http://github.com:25/t.git')
250
251

      expect(project2).to be_invalid
252
      expect(project2.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
253
254
    end

255
256
257
258
259
260
261
    it "does not allow import_url with invalid user" do
      project2 = build(:project, import_url: 'http://$user:password@github.com/t.git')

      expect(project2).to be_invalid
      expect(project2.errors[:import_url].first).to include('Username needs to start with an alphanumeric character')
    end

262
263
    describe 'project pending deletion' do
      let!(:project_pending_deletion) do
264
        create(:project,
265
266
267
               pending_delete: true)
      end
      let(:new_project) do
268
        build(:project,
269
270
271
272
273
274
275
276
277
278
279
280
              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
281
282
283

    describe 'path validation' do
      it 'allows paths reserved on the root namespace' do
284
        project = build(:project, path: 'api')
285
286
287
288
289

        expect(project).to be_valid
      end

      it 'rejects paths reserved on another level' do
290
        project = build(:project, path: 'tree')
291
292
293

        expect(project).not_to be_valid
      end
294
295
296

      it 'rejects nested paths' do
        parent = create(:group, :nested, path: 'environments')
297
        project = build(:project, path: 'folders', namespace: parent)
298
299
300

        expect(project).not_to be_valid
      end
301
302
303

      it 'allows a reserved group name' do
        parent = create(:group)
304
        project = build(:project, path: 'avatar', namespace: parent)
305
306
307

        expect(project).to be_valid
      end
308
309
310
311
312
313

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

        expect(project).to be_valid
      end
314
    end
gitlabhq's avatar
gitlabhq committed
315
  end
316

317
  describe 'project token' do
318
    it 'sets an random token if none provided' do
319
      project = FactoryBot.create(:project, runners_token: '')
Kamil Trzcinski's avatar
Kamil Trzcinski committed
320
      expect(project.runners_token).not_to eq('')
321
322
    end

ubudzisz's avatar
ubudzisz committed
323
    it 'does not set an random token if one provided' do
324
      project = FactoryBot.create(:project, runners_token: 'my-token')
Kamil Trzcinski's avatar
Kamil Trzcinski committed
325
      expect(project.runners_token).to eq('my-token')
326
327
    end
  end
gitlabhq's avatar
gitlabhq committed
328

329
  describe 'Respond to' do
330
331
332
333
334
    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) }
335
    it { is_expected.to respond_to(:full_path) }
gitlabhq's avatar
gitlabhq committed
336
337
  end

338
  describe 'delegation' do
339
340
341
342
343
344
    [: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(:members).to(:team).with_prefix(true) }
    it { is_expected.to delegate_method(:name).to(:owner).with_prefix(true).with_arguments(allow_nil: true) }
345
346
  end

347
348
349
350
351
352
353
354
355
  describe '#to_reference_with_postfix' do
    it 'returns the full path with reference_postfix' do
      namespace = create(:namespace, path: 'sample-namespace')
      project = create(:project, path: 'sample-project', namespace: namespace)

      expect(project.to_reference_with_postfix).to eq 'sample-namespace/sample-project>'
    end
  end

356
  describe '#to_reference' do
357
    let(:owner)     { create(:user, name: 'Gitlab') }
358
    let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
359
    let(:project)   { create(:project, path: 'sample-project', namespace: namespace) }
360
    let(:group)     { create(:group, name: 'Group', path: 'sample-group') }
361

362
    context 'when nil argument' do
363
364
365
366
367
      it 'returns nil' do
        expect(project.to_reference).to be_nil
      end
    end

368
    context 'when full is true' do
369
      it 'returns complete path to the project' do
370
371
372
        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'
373
374
375
376
377
378
379
380
381
382
      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
383
      let(:another_namespace_project) { create(:project, name: 'another-project') }
384
385
386
387
388
389
390

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

393
      it 'returns path to the project' do
394
395
396
        expect(project.to_reference(another_project)).to eq 'sample-project'
      end
    end
397

398
399
    context 'when different namespace / cross-project argument' do
      let(:another_namespace) { create(:namespace, path: 'another-namespace', owner: owner) }
400
      let(:another_project)   { create(:project, path: 'another-project', namespace: another_namespace) }
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417

      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
418
419
      end
    end
420
421
422
423
424
  end

  describe '#to_human_reference' do
    let(:owner) { create(:user, name: 'Gitlab') }
    let(:namespace) { create(:namespace, name: 'Sample namespace', owner: owner) }
425
    let(:project) { create(:project, name: 'Sample project', namespace: namespace) }
426
427
428
429
430
431
432
433
434
435
436
437
438
439

    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
440
      let(:another_namespace_project) { create(:project, name: 'another-project') }
441
442
443
444
445
446
447

      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
448
      let(:another_project) { create(:project, namespace: namespace) }
449
450
451
452

      it 'returns name of the project' do
        expect(project.to_human_reference(another_project)).to eq 'Sample project'
      end
453
454
455
    end
  end

456
  describe '#merge_method' do
457
458
459
460
461
462
463
    using RSpec::Parameterized::TableSyntax

    where(:ff, :rebase, :method) do
      true  | true  | :ff
      true  | false | :ff
      false | true  | :rebase_merge
      false | false | :merge
464
465
    end

466
467
468
469
470
471
    with_them do
      let(:project) { build(:project, merge_requests_rebase_enabled: rebase, merge_requests_ff_only_enabled: ff) }

      subject { project.merge_method }

      it { is_expected.to eq(method) }
472
473
474
    end
  end

475
  it 'returns valid url to repo' do
476
    project = described_class.new(path: 'somewhere')
477
    expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
gitlabhq's avatar
gitlabhq committed
478
479
  end

Douwe Maan's avatar
Douwe Maan committed
480
  describe "#web_url" do
481
    let(:project) { create(:project, path: "somewhere") }
Douwe Maan's avatar
Douwe Maan committed
482
483

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

Imre Farkas's avatar
Imre Farkas committed
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
  describe "#readme_url" do
    let(:project) { create(:project, :repository, path: "somewhere") }

    context 'with a non-existing repository' do
      it 'returns nil' do
        allow(project.repository).to receive(:tree).with(:head).and_return(nil)

        expect(project.readme_url).to be_nil
      end
    end

    context 'with an existing repository' do
      context 'when no README exists' do
        it 'returns nil' do
          allow_any_instance_of(Tree).to receive(:readme).and_return(nil)

          expect(project.readme_url).to be_nil
        end
      end

      context 'when a README exists' do
        it 'returns the README' do
          expect(project.readme_url).to eql("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere/blob/master/README.md")
        end
      end
    end
  end

516
  describe "#new_issuable_address" do
517
    let(:project) { create(:project, path: "somewhere") }
518
519
    let(:user) { create(:user) }

520
521
522
523
524
525
    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
526
        address = "p+#{project.full_path}+#{user.incoming_email_token}@gl.ab"
527

528
529
530
531
532
533
534
        expect(project.new_issuable_address(user, 'issue')).to eq(address)
      end

      it 'returns the address to create a new merge request' do
        address = "p+#{project.full_path}+merge-request+#{user.incoming_email_token}@gl.ab"

        expect(project.new_issuable_address(user, 'merge_request')).to eq(address)
535
536
537
538
539
540
541
      end
    end

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

543
      it 'returns nil' do
544
545
546
547
548
        expect(project.new_issuable_address(user, 'issue')).to be_nil
      end

      it 'returns nil' do
        expect(project.new_issuable_address(user, 'merge_request')).to be_nil
549
      end
550
551
552
    end
  end

553
  describe 'last_activity methods' do
554
555
    let(:timestamp) { 2.hours.ago }
    # last_activity_at gets set to created_at upon creation
556
    let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) }
gitlabhq's avatar
gitlabhq committed
557

558
    describe 'last_activity' do
559
      it 'alias last_activity to last_event' do
560
        last_event = create(:event, :closed, project: project)
561

562
        expect(project.last_activity).to eq(last_event)
563
      end
gitlabhq's avatar
gitlabhq committed
564
565
    end

566
567
    describe 'last_activity_date' do
      it 'returns the creation date of the project\'s last event if present' do
568
        new_event = create(:event, :closed, project: project, created_at: Time.now)
569

570
        project.reload
571
        expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i)
572
      end
573

574
      it 'returns the project\'s last update date if it has no events' do
575
        expect(project.last_activity_date).to eq(project.updated_at)
576
      end
577
578
579
580
581
582

      it 'returns the most recent timestamp' do
        project.update_attributes(updated_at: nil,
                                  last_activity_at: timestamp,
                                  last_repository_updated_at: timestamp - 1.hour)

583
        expect(project.last_activity_date).to be_like_time(timestamp)
584
585
586
587
588

        project.update_attributes(updated_at: timestamp,
                                  last_activity_at: timestamp - 1.hour,
                                  last_repository_updated_at: nil)

589
        expect(project.last_activity_date).to be_like_time(timestamp)
590
      end
591
592
    end
  end
593

594
  describe '#get_issue' do
595
    let(:project) { create(:project) }
Stan Hu's avatar
Stan Hu committed
596
    let!(:issue)  { create(:issue, project: project) }
597
598
599
    let(:user)    { create(:user) }

    before do
600
      project.add_developer(user)
601
    end
602
603
604

    context 'with default issues tracker' do
      it 'returns an issue' do
605
        expect(project.get_issue(issue.iid, user)).to eq issue
606
607
      end

Stan Hu's avatar
Stan Hu committed
608
609
610
611
      it 'returns count of open issues' do
        expect(project.open_issues_count).to eq(1)
      end

612
      it 'returns nil when no issue found' do
613
614
615
616
617
618
        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
619
620
621
622
      end
    end

    context 'with external issues tracker' do
623
      let!(:internal_issue) { create(:issue, project: project) }
624
      before do
625
        allow(project).to receive(:external_issue_tracker).and_return(true)
626
627
      end

628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
      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
665
666
667
668
669
      end
    end
  end

  describe '#issue_exists?' do
670
    let(:project) { create(:project) }
671
672
673
674
675
676
677
678
679
680
681
682

    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

683
  describe '#to_param' do
684
685
    context 'with namespace' do
      before do
686
        @group = create(:group, name: 'gitlab')
687
        @project = create(:project, name: 'gitlabhq', namespace: @group)
688
689
      end

Vinnie Okada's avatar
Vinnie Okada committed
690
      it { expect(@project.to_param).to eq('gitlabhq') }
691
    end
692
693
694

    context 'with invalid path' do
      it 'returns previous path to keep project suitable for use in URLs when persisted' do
695
        project = create(:project, path: 'gitlab')
696
697
698
699
700
701
702
        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
703
        project = build(:project, path: 'gitlab')
704
705
706
707
708
709
        project.path = 'foo&bar'

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

712
  describe '#repository' do
713
    let(:project) { create(:project, :repository) }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
714

715
    it 'returns valid repo' do
716
      expect(project.repository).to be_kind_of(Repository)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
717
718
    end
  end
719

720
  describe '#default_issues_tracker?' do
721
    it "is true if used internal tracker" do
722
      project = build(:project)
723

724
      expect(project.default_issues_tracker?).to be_truthy
725
726
    end

727
    it "is false if used other tracker" do
728
729
730
731
      # NOTE: The current nature of this factory requires persistence
      project = create(:redmine_project)

      expect(project.default_issues_tracker?).to be_falsey
732
733
734
    end
  end

735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
  describe '#empty_repo?' do
    context 'when the repo does not exist' do
      let(:project) { build_stubbed(:project) }

      it 'returns true' do
        expect(project.empty_repo?).to be(true)
      end
    end

    context 'when the repo exists' do
      let(:project) { create(:project, :repository) }
      let(:empty_project) { create(:project, :empty_repo) }

      it { expect(empty_project.empty_repo?).to be(true) }
      it { expect(project.empty_repo?).to be(false) }
    end
  end

753
  describe '#external_issue_tracker' do
754
    let(:project) { create(:project) }
755
756
757
    let(:ext_project) { create(:redmine_project) }

    context 'on existing projects with no value for has_external_issue_tracker' do
758
      before do
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
        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

788
  describe '#cache_has_external_issue_tracker' do
789
    let(:project) { create(:project, has_external_issue_tracker: nil) }
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807

    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
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845

    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
846
847
  end

848
  describe '#has_wiki?' do
849
850
851
    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) }
852
853
854
855
856
857
858
859

    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

860
  describe '#external_wiki' do
861
    let(:project) { create(:project) }
862

863
864
865
866
867
    context 'with an active external wiki' do
      before do
        create(:service, project: project, type: 'ExternalWikiService', active: true)
        project.external_wiki
      end
868

869
870
871
      it 'sets :has_external_wiki as true' do
        expect(project.has_external_wiki).to be(true)
      end
872

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

876
877
878
879
        project.services.external_wikis.first.destroy

        expect(project.has_external_wiki).to be(false)
      end
880
881
    end

882
883
884
885
    context 'with an inactive external wiki' do
      before do
        create(:service, project: project, type: 'ExternalWikiService', active: false)
      end
886

887
888
889
      it 'sets :has_external_wiki as false' do
        expect(project.has_external_wiki).to be(false)
      end
890
891
    end

892
893
894
895
    context 'with no external wiki' do
      before do
        project.external_wiki
      end
896

897
898
899
900
901
902
903
904
905
906
907
      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
908
909
910
    end
  end

911
912
  describe '#star_count' do
    it 'counts stars from multiple users' do
913
914
      user1 = create(:user)
      user2 = create(:user)
915
      project = create(:project, :public)
Ciro Santilli's avatar
Ciro Santilli committed
916
917

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

Ciro Santilli's avatar
Ciro Santilli committed
919
      user1.toggle_star(project)
920
921
      expect(project.reload.star_count).to eq(1)

Ciro Santilli's avatar
Ciro Santilli committed
922
      user2.toggle_star(project)
923
924
925
      project.reload
      expect(project.reload.star_count).to eq(2)

Ciro Santilli's avatar
Ciro Santilli committed
926
      user1.toggle_star(project)
927
928
929
      project.reload
      expect(project.reload.star_count).to eq(1)

Ciro Santilli's avatar
Ciro Santilli committed
930
      user2.toggle_star(project)
931
932
933
934
      project.reload
      expect(project.reload.star_count).to eq(0)
    end

935
    it 'counts stars on the right project' do
936
      user = create(:user)
937
938
      project1 = create(:project, :public)
      project2 = create(:project, :public)
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965

      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
966
967
    end
  end
968

969
  describe '#avatar_type' do
970
    let(:project) { create(:project) }
971

972
    it 'is true if avatar is image' do
973
      project.update_attribute(:avatar, 'uploads/avatar.png')
974
      expect(project.avatar_type).to be_truthy
975
976
    end

977
    it 'is false if avatar is html page' do
978
      project.update_attribute(:avatar, 'uploads/avatar.html')
979
      expect(project.avatar_type).to eq(['file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff, ico'])
980
981
    end
  end
sue445's avatar
sue445 committed
982

983
  describe '#avatar_url' do
sue445's avatar
sue445 committed
984
985
    subject { project.avatar_url }

986
    let(:project) { create(:project) }
sue445's avatar
sue445 committed
987

988
    context 'when avatar file is uploaded' do
989
      let(:project) { create(:project, :public, :with_avatar) }
sue445's avatar
sue445 committed
990

991
      it 'shows correct url' do
992
993
        expect(project.avatar_url).to eq(project.avatar.url)
        expect(project.avatar_url(only_path: false)).to eq([Gitlab.config.gitlab.url, project.avatar.url].join)
994
      end
sue445's avatar
sue445 committed
995
996
    end

997
    context 'when avatar file in git' do
sue445's avatar
sue445 committed
998
999
1000
1001
      before do
        allow(project).to receive(:avatar_in_git) { true }
      end

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

1004
      it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
sue445's avatar
sue445 committed
1005
    end
1006
1007

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

1010
      it { is_expected.to eq nil }
1011
    end
sue445's avatar
sue445 committed
1012
  end
1013

1014
  describe '#pipeline_for' do
1015
    let(:project) { create(:project, :repository) }
1016
    let!(:pipeline) { create_pipeline }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
1017

1018
1019
    shared_examples 'giving the correct pipeline' do
      it { is_expected.to eq(pipeline) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
1020

1021
1022
      context 'return latest' do
        let!(:pipeline2) { create_pipeline }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
1023

1024
        it { is_expected.to eq(pipeline2) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
1025
      end
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
    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
1039

1040
1041
1042
1043
1044
    def create_pipeline
      create(:ci_pipeline,
             project: project,
             ref: 'master',
             sha: project.commit('master').sha)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
1045
    end
1046
  end
1047

1048
  describe '#builds_enabled' do
1049
    let(:project) { create(:project) }
1050

1051
1052
1053
    subject { project.builds_enabled }

    it { expect(project.builds_enabled?).to be_truthy }
1054
  end
1055

1056
  describe '.with_shared_runners' do
1057
    subject { described_class.with_shared_runners }
1058
1059

    context 'when shared runners are enabled for project' do
1060
      let!(:project) { create(:project, shared_runners_enabled: true) }
1061
1062
1063
1064
1065
1066
1067

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

    context 'when shared runners are disabled for project' do
Robert Speicher's avatar