Read about our upcoming Code of Conduct on this issue

This instance will be upgraded to Heptapod 0.28.1 on 2022-01-26 at 16:00 UTC+1 (a few minutes of down time)

project_spec.rb 110 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
    it { is_expected.to have_many(:runner_projects) }
    it { is_expected.to have_many(:runners) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
66
    it { is_expected.to have_many(:active_runners) }
67
68
    it { is_expected.to have_many(:variables) }
    it { is_expected.to have_many(:triggers) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
69
    it { is_expected.to have_many(:pages_domains) }
70
71
72
73
74
75
76
77
78
    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
79
    it { is_expected.to have_many(:forks).through(:forked_project_links) }
80
    it { is_expected.to have_many(:uploads).dependent(:destroy) }
81
    it { is_expected.to have_many(:pipeline_schedules) }
82
    it { is_expected.to have_many(:members_and_requesters) }
83
    it { is_expected.to have_many(:clusters) }
84
    it { is_expected.to have_many(:custom_attributes).class_name('ProjectCustomAttribute') }
85
    it { is_expected.to have_many(:project_badges).class_name('ProjectBadge') }
86
    it { is_expected.to have_many(:lfs_file_locks) }
Mayra Cabrera's avatar
Mayra Cabrera committed
87
88
    it { is_expected.to have_many(:project_deploy_tokens) }
    it { is_expected.to have_many(:deploy_tokens).through(:project_deploy_tokens) }
89

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

96
97
98
99
100
101
102
103
104
    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

105
    describe '#members & #requesters' do
106
      let(:project) { create(:project, :public, :access_requestable) }
107
108
109
110
      let(:requester) { create(:user) }
      let(:developer) { create(:user) }
      before do
        project.request_access(requester)
111
        project.add_developer(developer)
112
113
      end

114
115
      it_behaves_like 'members and requesters associations' do
        let(:namespace) { project }
116
117
      end
    end
118
119
120
121
122

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

123
        expect { subject.boards.build }.to raise_error(Project::BoardLimitExceeded, 'Number of permitted boards exceeded')
124
125
126
        expect(subject.boards.size).to eq 1
      end
    end
gitlabhq's avatar
gitlabhq committed
127
128
  end

129
130
131
132
133
134
135
136
  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) }
137
138
  end

139
  describe 'validation' do
140
    let!(:project) { create(:project) }
141

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

146
    it { is_expected.to validate_presence_of(:path) }
147
148
149
150
    it { is_expected.to validate_length_of(:path).is_at_most(255) }

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

151
152
153
    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) }
154
    it { is_expected.not_to allow_value('/test/foo').for(:ci_config_path) }
155

156
    it { is_expected.to validate_presence_of(:creator) }
157

158
    it { is_expected.to validate_presence_of(:namespace) }
159

160
    it { is_expected.to validate_presence_of(:repository_storage) }
161

162
    it 'does not allow new projects beyond user limits' do
163
      project2 = build(:project)
164
165
      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
166
      expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/)
167
    end
168
169
170

    describe 'wiki path conflict' do
      context "when the new path has been used by the wiki of other Project" do
171
        it 'has an error on the name attribute' do
172
          new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki")
173
174
175
176
177
178
179

          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
180
        it 'has an error on the name attribute' do
181
182
          project_with_wiki_suffix = create(:project, path: 'foo.wiki')
          new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo')
183
184
185
186
187
188

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

190
    context 'repository storages inclusion' do
191
      let(:project2) { build(:project, repository_storage: 'missing') }
192
193

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

198
      it "does not allow repository storages that don't match a label in the configuration" do
199
200
201
202
        expect(project2).not_to be_valid
        expect(project2.errors[:repository_storage].first).to match(/is not included in the list/)
      end
    end
203

204
    it 'does not allow an invalid URI as import_url' do
205
      project2 = build(:project, import_url: 'invalid://')
James Lopez's avatar
James Lopez committed
206
207
208
209

      expect(project2).not_to be_valid
    end

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

213
214
      expect(project2).to be_valid
    end
215

216
    it 'allows an empty URI' do
217
      project2 = build(:project, import_url: '')
218

219
      expect(project2).to be_valid
220
221
222
    end

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

      expect(project2.import_data).to be_nil
    end

    it 'does not produce import data on an invalid URI' do
229
      project2 = build(:project, import_url: 'test://')
230
231
232

      expect(project2.import_data).to be_nil
    end
233

234
    it "does not allow blocked import_url localhost" do
235
      project2 = build(:project, import_url: 'http://localhost:9000/t.git')
236
237

      expect(project2).to be_invalid
238
      expect(project2.errors[:import_url].first).to include('Requests to localhost are not allowed')
239
240
241
    end

    it "does not allow blocked import_url port" do
242
      project2 = build(:project, import_url: 'http://github.com:25/t.git')
243
244

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

248
249
    describe 'project pending deletion' do
      let!(:project_pending_deletion) do
250
        create(:project,
251
252
253
               pending_delete: true)
      end
      let(:new_project) do
254
        build(:project,
255
256
257
258
259
260
261
262
263
264
265
266
              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
267
268
269

    describe 'path validation' do
      it 'allows paths reserved on the root namespace' do
270
        project = build(:project, path: 'api')
271
272
273
274
275

        expect(project).to be_valid
      end

      it 'rejects paths reserved on another level' do
276
        project = build(:project, path: 'tree')
277
278
279

        expect(project).not_to be_valid
      end
280
281
282

      it 'rejects nested paths' do
        parent = create(:group, :nested, path: 'environments')
283
        project = build(:project, path: 'folders', namespace: parent)
284
285
286

        expect(project).not_to be_valid
      end
287
288
289

      it 'allows a reserved group name' do
        parent = create(:group)
290
        project = build(:project, path: 'avatar', namespace: parent)
291
292
293

        expect(project).to be_valid
      end
294
295
296
297
298
299

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

        expect(project).to be_valid
      end
300
    end
gitlabhq's avatar
gitlabhq committed
301
  end
302

303
  describe 'project token' do
304
    it 'sets an random token if none provided' do
305
      project = FactoryBot.create :project, runners_token: ''
Kamil Trzcinski's avatar
Kamil Trzcinski committed
306
      expect(project.runners_token).not_to eq('')
307
308
    end

ubudzisz's avatar
ubudzisz committed
309
    it 'does not set an random token if one provided' do
310
      project = FactoryBot.create :project, runners_token: 'my-token'
Kamil Trzcinski's avatar
Kamil Trzcinski committed
311
      expect(project.runners_token).to eq('my-token')
312
313
    end
  end
gitlabhq's avatar
gitlabhq committed
314

315
  describe 'Respond to' do
316
317
318
319
320
    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) }
321
    it { is_expected.to respond_to(:full_path) }
gitlabhq's avatar
gitlabhq committed
322
323
  end

324
  describe 'delegation' do
325
326
327
328
329
330
    [: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) }
331
332
  end

333
  describe '#to_reference' do
334
    let(:owner)     { create(:user, name: 'Gitlab') }
335
    let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
336
    let(:project)   { create(:project, path: 'sample-project', namespace: namespace) }
337
    let(:group)     { create(:group, name: 'Group', path: 'sample-group', owner: owner) }
338

339
    context 'when nil argument' do
340
341
342
343
344
      it 'returns nil' do
        expect(project.to_reference).to be_nil
      end
    end

345
    context 'when full is true' do
346
      it 'returns complete path to the project' do
347
348
349
        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'
350
351
352
353
354
355
356
357
358
359
      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
360
      let(:another_namespace_project) { create(:project, name: 'another-project') }
361
362
363
364
365
366
367

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

370
      it 'returns path to the project' do
371
372
373
        expect(project.to_reference(another_project)).to eq 'sample-project'
      end
    end
374

375
376
    context 'when different namespace / cross-project argument' do
      let(:another_namespace) { create(:namespace, path: 'another-namespace', owner: owner) }
377
      let(:another_project)   { create(:project, path: 'another-project', namespace: another_namespace) }
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394

      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
395
396
      end
    end
397
398
399
400
401
  end

  describe '#to_human_reference' do
    let(:owner) { create(:user, name: 'Gitlab') }
    let(:namespace) { create(:namespace, name: 'Sample namespace', owner: owner) }
402
    let(:project) { create(:project, name: 'Sample project', namespace: namespace) }
403
404
405
406
407
408
409
410
411
412
413
414
415
416

    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
417
      let(:another_namespace_project) { create(:project, name: 'another-project') }
418
419
420
421
422
423
424

      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
425
      let(:another_project) { create(:project, namespace: namespace) }
426
427
428
429

      it 'returns name of the project' do
        expect(project.to_human_reference(another_project)).to eq 'Sample project'
      end
430
431
432
    end
  end

433
  describe '#merge_method' do
434
435
436
437
438
439
440
    using RSpec::Parameterized::TableSyntax

    where(:ff, :rebase, :method) do
      true  | true  | :ff
      true  | false | :ff
      false | true  | :rebase_merge
      false | false | :merge
441
442
    end

443
444
445
446
447
448
    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) }
449
450
451
    end
  end

452
  describe '#repository_storage_path' do
453
    let(:project) { create(:project) }
454
455

    it 'returns the repository storage path' do
456
      expect(Dir.exist?(project.repository_storage_path)).to be(true)
457
458
459
    end
  end

460
  it 'returns valid url to repo' do
461
    project = described_class.new(path: 'somewhere')
462
    expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
gitlabhq's avatar
gitlabhq committed
463
464
  end

Douwe Maan's avatar
Douwe Maan committed
465
  describe "#web_url" do
466
    let(:project) { create(:project, path: "somewhere") }
Douwe Maan's avatar
Douwe Maan committed
467
468

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

473
  describe "#new_issuable_address" do
474
    let(:project) { create(:project, path: "somewhere") }
475
476
    let(:user) { create(:user) }

477
478
479
480
481
482
    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
483
        address = "p+#{project.full_path}+#{user.incoming_email_token}@gl.ab"
484

485
486
487
488
489
490
491
        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)
492
493
494
495
496
497
498
      end
    end

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

500
      it 'returns nil' do
501
502
503
504
505
        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
506
      end
507
508
509
    end
  end

510
  describe 'last_activity methods' do
511
512
    let(:timestamp) { 2.hours.ago }
    # last_activity_at gets set to created_at upon creation
513
    let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) }
gitlabhq's avatar
gitlabhq committed
514

515
    describe 'last_activity' do
516
      it 'alias last_activity to last_event' do
517
        last_event = create(:event, :closed, project: project)
518

519
        expect(project.last_activity).to eq(last_event)
520
      end
gitlabhq's avatar
gitlabhq committed
521
522
    end

523
524
    describe 'last_activity_date' do
      it 'returns the creation date of the project\'s last event if present' do
525
        new_event = create(:event, :closed, project: project, created_at: Time.now)
526

527
        project.reload
528
        expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i)
529
      end
530

531
      it 'returns the project\'s last update date if it has no events' do
532
        expect(project.last_activity_date).to eq(project.updated_at)
533
      end
534
535
536
537
538
539
540
541
542
543
544
545
546
547

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

        expect(project.last_activity_date).to eq(timestamp)

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

        expect(project.last_activity_date).to eq(timestamp)
      end
548
549
    end
  end
550

551
  describe '#get_issue' do
552
    let(:project) { create(:project) }
Stan Hu's avatar
Stan Hu committed
553
    let!(:issue)  { create(:issue, project: project) }
554
555
556
    let(:user)    { create(:user) }

    before do
557
      project.add_developer(user)
558
    end
559
560
561

    context 'with default issues tracker' do
      it 'returns an issue' do
562
        expect(project.get_issue(issue.iid, user)).to eq issue
563
564
      end

Stan Hu's avatar
Stan Hu committed
565
566
567
568
      it 'returns count of open issues' do
        expect(project.open_issues_count).to eq(1)
      end

569
      it 'returns nil when no issue found' do
570
571
572
573
574
575
        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
576
577
578
579
      end
    end

    context 'with external issues tracker' do
580
      let!(:internal_issue) { create(:issue, project: project) }
581
      before do
582
        allow(project).to receive(:external_issue_tracker).and_return(true)
583
584
      end

585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
      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
622
623
624
625
626
      end
    end
  end

  describe '#issue_exists?' do
627
    let(:project) { create(:project) }
628
629
630
631
632
633
634
635
636
637
638
639

    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

640
  describe '#to_param' do
641
642
643
    context 'with namespace' do
      before do
        @group = create :group, name: 'gitlab'
644
        @project = create(:project, name: 'gitlabhq', namespace: @group)
645
646
      end

Vinnie Okada's avatar
Vinnie Okada committed
647
      it { expect(@project.to_param).to eq('gitlabhq') }
648
    end
649
650
651

    context 'with invalid path' do
      it 'returns previous path to keep project suitable for use in URLs when persisted' do
652
        project = create(:project, path: 'gitlab')
653
654
655
656
657
658
659
        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
660
        project = build(:project, path: 'gitlab')
661
662
663
664
665
666
        project.path = 'foo&bar'

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

669
  describe '#repository' do
670
    let(:project) { create(:project, :repository) }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
671

672
    it 'returns valid repo' do
673
      expect(project.repository).to be_kind_of(Repository)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
674
675
    end
  end
676

677
  describe '#default_issues_tracker?' do
678
    it "is true if used internal tracker" do
679
      project = build(:project)
680

681
      expect(project.default_issues_tracker?).to be_truthy
682
683
    end

684
    it "is false if used other tracker" do
685
686
687
688
      # NOTE: The current nature of this factory requires persistence
      project = create(:redmine_project)

      expect(project.default_issues_tracker?).to be_falsey
689
690
691
    end
  end

692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
  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

710
  describe '#external_issue_tracker' do
711
    let(:project) { create(:project) }
712
713
714
    let(:ext_project) { create(:redmine_project) }

    context 'on existing projects with no value for has_external_issue_tracker' do
715
      before do
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
741
742
743
744
        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

745
  describe '#cache_has_external_issue_tracker' do
746
    let(:project) { create(:project, has_external_issue_tracker: nil) }
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764

    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
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802

    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
803
804
  end

805
  describe '#has_wiki?' do
806
807
808
    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) }
809
810
811
812
813
814
815
816

    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

817
  describe '#external_wiki' do
818
    let(:project) { create(:project) }
819

820
821
822
823
824
    context 'with an active external wiki' do
      before do
        create(:service, project: project, type: 'ExternalWikiService', active: true)
        project.external_wiki
      end
825

826
827
828
      it 'sets :has_external_wiki as true' do
        expect(project.has_external_wiki).to be(true)
      end
829

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

833
834
835
836
        project.services.external_wikis.first.destroy

        expect(project.has_external_wiki).to be(false)
      end
837
838
    end

839
840
841
842
    context 'with an inactive external wiki' do
      before do
        create(:service, project: project, type: 'ExternalWikiService', active: false)
      end
843

844
845
846
      it 'sets :has_external_wiki as false' do
        expect(project.has_external_wiki).to be(false)
      end
847
848
    end

849
850
851
852
    context 'with no external wiki' do
      before do
        project.external_wiki
      end
853

854
855
856
857
858
859
860
861
862
863
864
      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
865
866
867
    end
  end

868
869
  describe '#star_count' do
    it 'counts stars from multiple users' do
Ciro Santilli's avatar
Ciro Santilli committed
870
871
      user1 = create :user
      user2 = create :user
872
      project = create(:project, :public)
Ciro Santilli's avatar
Ciro Santilli committed
873
874

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

Ciro Santilli's avatar
Ciro Santilli committed
876
      user1.toggle_star(project)
877
878
      expect(project.reload.star_count).to eq(1)

Ciro Santilli's avatar
Ciro Santilli committed
879
      user2.toggle_star(project)
880
881
882
      project.reload
      expect(project.reload.star_count).to eq(2)

Ciro Santilli's avatar
Ciro Santilli committed
883
      user1.toggle_star(project)
884
885
886
      project.reload
      expect(project.reload.star_count).to eq(1)

Ciro Santilli's avatar
Ciro Santilli committed
887
      user2.toggle_star(project)
888
889
890
891
      project.reload
      expect(project.reload.star_count).to eq(0)
    end

892
    it 'counts stars on the right project' do
893
      user = create :user
894
895
      project1 = create(:project, :public)
      project2 = create(:project, :public)
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922

      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
923
924
    end
  end
925

926
  describe '#avatar_type' do
927
    let(:project) { create(:project) }
928

929
    it 'is true if avatar is image' do
930
      project.update_attribute(:avatar, 'uploads/avatar.png')
931
      expect(project.avatar_type).to be_truthy
932
933
    end

934
    it 'is false if avatar is html page' do
935
      project.update_attribute(:avatar, 'uploads/avatar.html')
936
      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'])
937
938
    end
  end
sue445's avatar
sue445 committed
939

940
  describe '#avatar_url' do
sue445's avatar
sue445 committed
941
942
    subject { project.avatar_url }

943
    let(:project) { create(:project) }
sue445's avatar
sue445 committed
944

945
    context 'when avatar file is uploaded' do
946
      let(:project) { create(:project, :public, :with_avatar) }
sue445's avatar
sue445 committed
947

948
      it 'shows correct url' do
949
950
        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)
951
      end
sue445's avatar
sue445 committed
952
953
    end

954
    context 'when avatar file in git' do
sue445's avatar
sue445 committed
955
956
957
958
      before do
        allow(project).to receive(:avatar_in_git) { true }
      end

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

961
      it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
sue445's avatar
sue445 committed
962
    end
963
964

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

967
      it { is_expected.to eq nil }
968
    end
sue445's avatar
sue445 committed
969
  end
970

971
  describe '#pipeline_for' do
972
    let(:project) { create(:project, :repository) }
973
    let!(:pipeline) { create_pipeline }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
974

975
976
    shared_examples 'giving the correct pipeline' do
      it { is_expected.to eq(pipeline) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
977

978
979
      context 'return latest' do
        let!(:pipeline2) { create_pipeline }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
980

981
        it { is_expected.to eq(pipeline2) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
982
      end
983
984
985
986
987
988
989
990
991
992
993
994
995
    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
996

997
998
999
1000
1001
    def create_pipeline
      create(:ci_pipeline,
             project: project,
             ref: 'master',
             sha: project.commit('master').sha)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
1002
    end
1003
  end
1004

1005
  describe '#builds_enabled' do
1006
    let(:project) { create(:project) }
1007

1008
1009
1010
    subject { project.builds_enabled }

    it { expect(project.builds_enabled?).to be_truthy }
1011
  end
1012

1013
  describe '.with_shared_runners' do
1014
    subject { described_class.with_shared_runners }
1015
1016

    context 'when shared runners are enabled for project' do
1017
      let!(:project) { create(:project, shared_runners_enabled: true) }
1018
1019
1020
1021
1022
1023
1024

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

    context 'when shared runners are disabled for project' do
1025
      let!(:project) { create(:project, shared_runners_enabled: false) }
1026
1027
1028
1029
1030
1031
1032

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

1033
  describe '.cached_count', :use_clean_rails_memory_store_caching do
1034
    let(:group)     { create(:group, :public) }
1035
1036
    let!(:project1) { create(:project, :public, group: group) }
    let!(:project2) { create(:project, :public, group: group) }
1037
1038

    it 'returns total project count' do
1039
      expect(described_class).to receive(:count).once.and_call_original
1040
1041

      3.times do
1042
        expect(described_class.cached_count).to eq(2)
1043
1044
1045