Read about our upcoming Code of Conduct on this issue

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

3
describe Project do
4
  include ProjectForksHelper
5
  include GitHelpers
6

7
  describe 'associations' do
8
9
10
11
    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
12
    it { is_expected.to have_many(:services) }
13
14
15
16
17
    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) }
18
    it { is_expected.to have_many(:users).through(:project_members) }
19
20
21
22
    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) }
23
    it { is_expected.to have_many(:deploy_keys) }
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(:slack_service) }
    it { is_expected.to have_one(:microsoft_teams_service) }
    it { is_expected.to have_one(:mattermost_service) }
29
    it { is_expected.to have_one(:hangouts_chat_service) }
Matt Coleman's avatar
Matt Coleman committed
30
    it { is_expected.to have_one(:packagist_service) }
31
32
33
34
    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) }
blackst0ne's avatar
blackst0ne committed
35
    it { is_expected.to have_one(:discord_service) }
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    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(: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
58
    it { is_expected.to have_one(:last_event).class_name('Event') }
59
    it { is_expected.to have_one(:forked_from_project).through(:fork_network_member) }
Zeger-Jan van de Weg's avatar
Zeger-Jan van de Weg committed
60
    it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
61
    it { is_expected.to have_many(:commit_statuses) }
62
    it { is_expected.to have_many(:pipelines) }
63
    it { is_expected.to have_many(:builds) }
64
    it { is_expected.to have_many(:build_trace_section_names)}
65
66
67
68
    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
69
    it { is_expected.to have_many(:pages_domains) }
70
71
    it { is_expected.to have_many(:labels).class_name('ProjectLabel') }
    it { is_expected.to have_many(:users_star_projects) }
72
    it { is_expected.to have_many(:repository_languages) }
73
74
75
76
77
78
79
    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) }
80
81
    it { is_expected.to have_many(:forked_to_members).class_name('ForkNetworkMember') }
    it { is_expected.to have_many(:forks).through(:forked_to_members) }
Jan Provaznik's avatar
Jan Provaznik committed
82
    it { is_expected.to have_many(:uploads) }
83
    it { is_expected.to have_many(:pipeline_schedules) }
84
    it { is_expected.to have_many(:members_and_requesters) }
85
    it { is_expected.to have_many(:clusters) }
86
    it { is_expected.to have_many(:custom_attributes).class_name('ProjectCustomAttribute') }
87
    it { is_expected.to have_many(:project_badges).class_name('ProjectBadge') }
88
    it { is_expected.to have_many(:lfs_file_locks) }
Mayra Cabrera's avatar
Mayra Cabrera committed
89
90
    it { is_expected.to have_many(:project_deploy_tokens) }
    it { is_expected.to have_many(:deploy_tokens).through(:project_deploy_tokens) }
91

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

98
99
100
101
102
103
104
105
106
    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

107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
    context 'Site Statistics' do
      context 'when creating a new project' do
        it 'tracks project in SiteStatistic' do
          expect { create(:project) }.to change { SiteStatistic.fetch.repositories_count }.by(1)
        end
      end

      context 'when deleting a project' do
        it 'untracks project in SiteStatistic' do
          project = create(:project)

          expect { project.destroy }.to change { SiteStatistic.fetch.repositories_count }.by(-1)
        end
      end
    end

123
124
125
126
    context 'updating cd_cd_settings' do
      it 'does not raise an error' do
        project = create(:project)

James Lopez's avatar
James Lopez committed
127
        expect { project.update(ci_cd_settings: nil) }.not_to raise_exception
128
129
130
      end
    end

131
    describe '#members & #requesters' do
132
      let(:project) { create(:project, :public, :access_requestable) }
133
134
135
136
      let(:requester) { create(:user) }
      let(:developer) { create(:user) }
      before do
        project.request_access(requester)
137
        project.add_developer(developer)
138
139
      end

140
141
      it_behaves_like 'members and requesters associations' do
        let(:namespace) { project }
142
143
      end
    end
144
145
146
147
148

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

149
        expect { subject.boards.build }.to raise_error(Project::BoardLimitExceeded, 'Number of permitted boards exceeded')
150
151
152
        expect(subject.boards.size).to eq 1
      end
    end
gitlabhq's avatar
gitlabhq committed
153
154
  end

155
156
157
158
159
160
161
162
  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) }
163
164
  end

165
  describe 'validation' do
166
    let!(:project) { create(:project) }
167

168
169
    it { is_expected.to validate_presence_of(:name) }
    it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) }
170
    it { is_expected.to validate_length_of(:name).is_at_most(255) }
171
    it { is_expected.to validate_presence_of(:path) }
172
173
    it { is_expected.to validate_length_of(:path).is_at_most(255) }
    it { is_expected.to validate_length_of(:description).is_at_most(2000) }
174
175
176
    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) }
177
    it { is_expected.not_to allow_value('/test/foo').for(:ci_config_path) }
178
179
    it { is_expected.to validate_presence_of(:creator) }
    it { is_expected.to validate_presence_of(:namespace) }
180
    it { is_expected.to validate_presence_of(:repository_storage) }
181

182
183
184
185
186
187
188
189
    it 'validates build timeout constraints' do
      is_expected.to validate_numericality_of(:build_timeout)
        .only_integer
        .is_greater_than_or_equal_to(10.minutes)
        .is_less_than(1.month)
        .with_message('needs to be beetween 10 minutes and 1 month')
    end

190
    it 'does not allow new projects beyond user limits' do
191
      project2 = build(:project)
192
193
      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
194
      expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/)
195
    end
196
197
198

    describe 'wiki path conflict' do
      context "when the new path has been used by the wiki of other Project" do
199
        it 'has an error on the name attribute' do
200
          new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki")
201
202
203
204
205
206
207

          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
208
        it 'has an error on the name attribute' do
209
210
          project_with_wiki_suffix = create(:project, path: 'foo.wiki')
          new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo')
211
212
213
214
215
216

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

218
    context 'repository storages inclusion' do
219
      let(:project2) { build(:project, repository_storage: 'missing') }
220
221

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

226
      it "does not allow repository storages that don't match a label in the configuration" do
227
228
229
230
        expect(project2).not_to be_valid
        expect(project2.errors[:repository_storage].first).to match(/is not included in the list/)
      end
    end
231

232
    it 'does not allow an invalid URI as import_url' do
233
      project = build(:project, import_url: 'invalid://')
James Lopez's avatar
James Lopez committed
234

235
236
237
238
239
240
241
242
243
244
245
246
247
248
      expect(project).not_to be_valid
    end

    it 'does allow a SSH URI as import_url for persisted projects' do
      project = create(:project)
      project.import_url = 'ssh://test@gitlab.com/project.git'

      expect(project).to be_valid
    end

    it 'does not allow a SSH URI as import_url for new projects' do
      project = build(:project, import_url: 'ssh://test@gitlab.com/project.git')

      expect(project).not_to be_valid
James Lopez's avatar
James Lopez committed
249
250
    end

251
    it 'does allow a valid URI as import_url' do
252
      project = build(:project, import_url: 'http://gitlab.com/project.git')
James Lopez's avatar
James Lopez committed
253

254
      expect(project).to be_valid
255
    end
256

257
    it 'allows an empty URI' do
258
      project = build(:project, import_url: '')
259

260
      expect(project).to be_valid
261
262
263
    end

    it 'does not produce import data on an empty URI' do
264
      project = build(:project, import_url: '')
265

266
      expect(project.import_data).to be_nil
267
268
269
    end

    it 'does not produce import data on an invalid URI' do
270
      project = build(:project, import_url: 'test://')
271

272
      expect(project.import_data).to be_nil
273
    end
274

275
    it "does not allow import_url pointing to localhost" do
276
277
278
279
280
281
282
283
      project = build(:project, import_url: 'http://localhost:9000/t.git')

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

    it "does not allow import_url with invalid ports for new projects" do
      project = build(:project, import_url: 'http://github.com:25/t.git')
284

285
286
      expect(project).to be_invalid
      expect(project.errors[:import_url].first).to include('Only allowed ports are 80, 443')
287
288
    end

289
290
291
    it "does not allow import_url with invalid ports for persisted projects" do
      project = create(:project)
      project.import_url = 'http://github.com:25/t.git'
292

293
294
      expect(project).to be_invalid
      expect(project.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
295
296
    end

297
    it "does not allow import_url with invalid user" do
298
      project = build(:project, import_url: 'http://$user:password@github.com/t.git')
299

300
301
      expect(project).to be_invalid
      expect(project.errors[:import_url].first).to include('Username needs to start with an alphanumeric character')
302
303
    end

304
305
    describe 'project pending deletion' do
      let!(:project_pending_deletion) do
306
        create(:project,
307
308
309
               pending_delete: true)
      end
      let(:new_project) do
310
        build(:project,
311
312
313
314
315
316
317
318
319
320
321
322
              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
323
324
325

    describe 'path validation' do
      it 'allows paths reserved on the root namespace' do
326
        project = build(:project, path: 'api')
327
328
329
330
331

        expect(project).to be_valid
      end

      it 'rejects paths reserved on another level' do
332
        project = build(:project, path: 'tree')
333
334
335

        expect(project).not_to be_valid
      end
336
337
338

      it 'rejects nested paths' do
        parent = create(:group, :nested, path: 'environments')
339
        project = build(:project, path: 'folders', namespace: parent)
340
341
342

        expect(project).not_to be_valid
      end
343
344
345

      it 'allows a reserved group name' do
        parent = create(:group)
346
        project = build(:project, path: 'avatar', namespace: parent)
347
348
349

        expect(project).to be_valid
      end
350
351
352
353
354
355

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

        expect(project).to be_valid
      end
356
    end
gitlabhq's avatar
gitlabhq committed
357
  end
358

359
  describe 'project token' do
360
    it 'sets an random token if none provided' do
361
      project = FactoryBot.create(:project, runners_token: '')
Kamil Trzcinski's avatar
Kamil Trzcinski committed
362
      expect(project.runners_token).not_to eq('')
363
364
    end

ubudzisz's avatar
ubudzisz committed
365
    it 'does not set an random token if one provided' do
366
      project = FactoryBot.create(:project, runners_token: 'my-token')
Kamil Trzcinski's avatar
Kamil Trzcinski committed
367
      expect(project.runners_token).to eq('my-token')
368
369
    end
  end
gitlabhq's avatar
gitlabhq committed
370

371
  describe 'Respond to' do
372
373
374
375
376
    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) }
377
    it { is_expected.to respond_to(:full_path) }
gitlabhq's avatar
gitlabhq committed
378
379
  end

380
  describe 'delegation' do
381
    [:add_guest, :add_reporter, :add_developer, :add_maintainer, :add_user, :add_users].each do |method|
382
383
384
385
386
      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) }
387
388
  end

389
390
391
392
393
394
395
396
397
  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

398
  describe '#to_reference' do
399
    let(:owner)     { create(:user, name: 'Gitlab') }
400
    let(:namespace) { create(:namespace, path: 'sample-namespace', owner: owner) }
401
    let(:project)   { create(:project, path: 'sample-project', namespace: namespace) }
402
    let(:group)     { create(:group, name: 'Group', path: 'sample-group') }
403

404
    context 'when nil argument' do
405
406
407
408
409
      it 'returns nil' do
        expect(project.to_reference).to be_nil
      end
    end

410
    context 'when full is true' do
411
      it 'returns complete path to the project' do
412
413
414
        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'
415
416
417
418
419
420
421
422
423
424
      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
425
      let(:another_namespace_project) { create(:project, name: 'another-project') }
426
427
428
429
430
431
432

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

435
      it 'returns path to the project' do
436
437
438
        expect(project.to_reference(another_project)).to eq 'sample-project'
      end
    end
439

440
441
    context 'when different namespace / cross-project argument' do
      let(:another_namespace) { create(:namespace, path: 'another-namespace', owner: owner) }
442
      let(:another_project)   { create(:project, path: 'another-project', namespace: another_namespace) }
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459

      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
460
461
      end
    end
462
463
464
465
466
  end

  describe '#to_human_reference' do
    let(:owner) { create(:user, name: 'Gitlab') }
    let(:namespace) { create(:namespace, name: 'Sample namespace', owner: owner) }
467
    let(:project) { create(:project, name: 'Sample project', namespace: namespace) }
468
469
470
471
472
473
474
475
476
477
478
479
480
481

    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
482
      let(:another_namespace_project) { create(:project, name: 'another-project') }
483
484
485
486
487
488
489

      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
490
      let(:another_project) { create(:project, namespace: namespace) }
491
492
493
494

      it 'returns name of the project' do
        expect(project.to_human_reference(another_project)).to eq 'Sample project'
      end
495
496
497
    end
  end

498
  describe '#merge_method' do
499
500
501
502
503
504
505
    using RSpec::Parameterized::TableSyntax

    where(:ff, :rebase, :method) do
      true  | true  | :ff
      true  | false | :ff
      false | true  | :rebase_merge
      false | false | :merge
506
507
    end

508
509
510
511
512
513
    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) }
514
515
516
    end
  end

517
  it 'returns valid url to repo' do
518
    project = described_class.new(path: 'somewhere')
519
    expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
gitlabhq's avatar
gitlabhq committed
520
521
  end

Douwe Maan's avatar
Douwe Maan committed
522
  describe "#web_url" do
523
    let(:project) { create(:project, path: "somewhere") }
Douwe Maan's avatar
Douwe Maan committed
524
525

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

Imre Farkas's avatar
Imre Farkas committed
530
531
  describe "#readme_url" do
    context 'with a non-existing repository' do
532
      let(:project) { create(:project) }
Imre Farkas's avatar
Imre Farkas committed
533

534
      it 'returns nil' do
Imre Farkas's avatar
Imre Farkas committed
535
536
537
538
539
540
        expect(project.readme_url).to be_nil
      end
    end

    context 'with an existing repository' do
      context 'when no README exists' do
541
        let(:project) { create(:project, :empty_repo) }
Imre Farkas's avatar
Imre Farkas committed
542

543
        it 'returns nil' do
Imre Farkas's avatar
Imre Farkas committed
544
545
546
547
548
          expect(project.readme_url).to be_nil
        end
      end

      context 'when a README exists' do
549
550
        let(:project) { create(:project, :repository) }

Imre Farkas's avatar
Imre Farkas committed
551
        it 'returns the README' do
552
          expect(project.readme_url).to eq("#{project.web_url}/blob/master/README.md")
Imre Farkas's avatar
Imre Farkas committed
553
554
555
556
557
        end
      end
    end
  end

558
  describe "#new_issuable_address" do
559
    let(:project) { create(:project, path: "somewhere") }
560
561
    let(:user) { create(:user) }

562
563
564
565
566
567
    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
568
        address = "p+#{project.full_path}+#{user.incoming_email_token}@gl.ab"
569

570
571
572
573
574
575
576
        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)
577
578
579
580
581
582
583
      end
    end

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

585
      it 'returns nil' do
586
587
588
589
590
        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
591
      end
592
593
594
    end
  end

595
  describe 'last_activity methods' do
596
597
    let(:timestamp) { 2.hours.ago }
    # last_activity_at gets set to created_at upon creation
598
    let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) }
gitlabhq's avatar
gitlabhq committed
599

600
    describe 'last_activity' do
601
      it 'alias last_activity to last_event' do
602
        last_event = create(:event, :closed, project: project)
603

604
        expect(project.last_activity).to eq(last_event)
605
      end
gitlabhq's avatar
gitlabhq committed
606
607
    end

608
609
    describe 'last_activity_date' do
      it 'returns the creation date of the project\'s last event if present' do
610
        new_event = create(:event, :closed, project: project, created_at: Time.now)
611

612
        project.reload
613
        expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i)
614
      end
615

616
      it 'returns the project\'s last update date if it has no events' do
617
        expect(project.last_activity_date).to eq(project.updated_at)
618
      end
619
620

      it 'returns the most recent timestamp' do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
621
622
623
        project.update(updated_at: nil,
                       last_activity_at: timestamp,
                       last_repository_updated_at: timestamp - 1.hour)
624

625
        expect(project.last_activity_date).to be_like_time(timestamp)
626

Lin Jen-Shin's avatar
Lin Jen-Shin committed
627
628
629
        project.update(updated_at: timestamp,
                       last_activity_at: timestamp - 1.hour,
                       last_repository_updated_at: nil)
630

631
        expect(project.last_activity_date).to be_like_time(timestamp)
632
      end
633
634
    end
  end
635

636
  describe '#get_issue' do
637
    let(:project) { create(:project) }
Stan Hu's avatar
Stan Hu committed
638
    let!(:issue)  { create(:issue, project: project) }
639
640
641
    let(:user)    { create(:user) }

    before do
642
      project.add_developer(user)
643
    end
644
645
646

    context 'with default issues tracker' do
      it 'returns an issue' do
647
        expect(project.get_issue(issue.iid, user)).to eq issue
648
649
      end

Stan Hu's avatar
Stan Hu committed
650
651
652
653
      it 'returns count of open issues' do
        expect(project.open_issues_count).to eq(1)
      end

654
      it 'returns nil when no issue found' do
655
656
657
658
659
660
        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
661
662
663
664
      end
    end

    context 'with external issues tracker' do
665
      let!(:internal_issue) { create(:issue, project: project) }
666
      before do
667
        allow(project).to receive(:external_issue_tracker).and_return(true)
668
669
      end

670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
      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
707
708
709
710
711
      end
    end
  end

  describe '#issue_exists?' do
712
    let(:project) { create(:project) }
713
714
715
716
717
718
719
720
721
722
723
724

    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

725
  describe '#to_param' do
726
727
    context 'with namespace' do
      before do
728
        @group = create(:group, name: 'gitlab')
729
        @project = create(:project, name: 'gitlabhq', namespace: @group)
730
731
      end

Vinnie Okada's avatar
Vinnie Okada committed
732
      it { expect(@project.to_param).to eq('gitlabhq') }
733
    end
734
735
736

    context 'with invalid path' do
      it 'returns previous path to keep project suitable for use in URLs when persisted' do
737
        project = create(:project, path: 'gitlab')
738
739
740
741
742
743
744
        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
745
        project = build(:project, path: 'gitlab')
746
747
748
749
750
751
        project.path = 'foo&bar'

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

754
  describe '#repository' do
755
    let(:project) { create(:project, :repository) }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
756

757
    it 'returns valid repo' do
758
      expect(project.repository).to be_kind_of(Repository)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
759
760
    end
  end
761

762
  describe '#default_issues_tracker?' do
763
    it "is true if used internal tracker" do
764
      project = build(:project)
765

766
      expect(project.default_issues_tracker?).to be_truthy
767
768
    end

769
    it "is false if used other tracker" do
770
771
772
773
      # NOTE: The current nature of this factory requires persistence
      project = create(:redmine_project)

      expect(project.default_issues_tracker?).to be_falsey
774
775
776
    end
  end

777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
  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

795
  describe '#external_issue_tracker' do
796
    let(:project) { create(:project) }
797
798
799
    let(:ext_project) { create(:redmine_project) }

    context 'on existing projects with no value for has_external_issue_tracker' do
800
      before do
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
        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

830
  describe '#cache_has_external_issue_tracker' do
831
    let(:project) { create(:project, has_external_issue_tracker: nil) }
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849

    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
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887

    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
888
889
  end

890
  describe '#has_wiki?' do
891
892
893
    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) }
894
895
896
897
898
899
900
901

    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

902
  describe '#external_wiki' do
903
    let(:project) { create(:project) }
904

905
906
907
908
909
    context 'with an active external wiki' do
      before do
        create(:service, project: project, type: 'ExternalWikiService', active: true)
        project.external_wiki
      end
910

911
912
913
      it 'sets :has_external_wiki as true' do
        expect(project.has_external_wiki).to be(true)
      end
914

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

918
919
920
921
        project.services.external_wikis.first.destroy

        expect(project.has_external_wiki).to be(false)
      end
922
923
    end

924
925
926
927
    context 'with an inactive external wiki' do
      before do
        create(:service, project: project, type: 'ExternalWikiService', active: false)
      end
928

929
930
931
      it 'sets :has_external_wiki as false' do
        expect(project.has_external_wiki).to be(false)
      end
932
933
    end

934
935
936
937
    context 'with no external wiki' do
      before do
        project.external_wiki
      end
938

939
940
941
942
943
944
945
946
947
948
949
      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
950
951
952
    end
  end

953
954
  describe '#star_count' do
    it 'counts stars from multiple users' do
955
956
      user1 = create(:user)
      user2 = create(:user)
957
      project = create(:project, :public)
Ciro Santilli's avatar
Ciro Santilli committed
958
959

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

Ciro Santilli's avatar
Ciro Santilli committed
961
      user1.toggle_star(project)
962
963
      expect(project.reload.star_count).to eq(1)

Ciro Santilli's avatar
Ciro Santilli committed
964
      user2.toggle_star(project)
965
966
967
      project.reload
      expect(project.reload.star_count).to eq(2)

Ciro Santilli's avatar
Ciro Santilli committed
968
      user1.toggle_star(project)
969
970
971
      project.reload
      expect(project.reload.star_count).to eq(1)

Ciro Santilli's avatar
Ciro Santilli committed
972
      user2.toggle_star(project)
973
974
975
976
      project.reload
      expect(project.reload.star_count).to eq(0)
    end

977
    it 'counts stars on the right project' do
978
      user = create(:user)
979
980
      project1 = create(:project, :public)
      project2 = create(:project, :public)
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007

      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
1008
1009
    end
  end
1010

1011
  describe '#avatar_type' do
1012
    let(:project) { create(:project) }
1013

1014
    it 'is true if avatar is image' do
1015
      project.update_attribute(:avatar, 'uploads/avatar.png')
1016
      expect(project.avatar_type).to be_truthy
1017
1018
    end

1019
    it 'is false if avatar is html page' do
1020
      project.update_attribute(:avatar, 'uploads/avatar.html')
1021
      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'])
1022
1023
    end
  end
sue445's avatar
sue445 committed
1024

1025
  describe '#avatar_url' do
sue445's avatar
sue445 committed
1026
1027
    subject { project.avatar_url }

1028
    let(:project) { create(:project) }
sue445's avatar
sue445 committed
1029

1030
    context 'when avatar file is uploaded' do
1031
      let(:project) { create(:project, :public, :with_avatar) }
sue445's avatar
sue445 committed
1032

1033
      it 'shows correct url' do
1034
1035
        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)
1036
      end
sue445's avatar
sue445 committed
1037
1038
    end

1039
    context 'when avatar file in git' do
sue445's avatar
sue445 committed
1040
1041
1042
1043
      before do
        allow(project).to receive(:avatar_in_git) { true }
      end

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

1046
      it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
sue445's avatar
sue445 committed
1047
    end
1048
1049

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

1052
      it { is_expected.to eq nil }