project_spec.rb 188.1 KB
Newer Older
1 2
# frozen_string_literal: true

G
gitlabhq 已提交
3 4
require 'spec_helper'

5
describe Project do
6
  include ProjectForksHelper
7
  include GitHelpers
8
  include ExternalAuthorizationServiceHelpers
9
  using RSpec::Parameterized::TableSyntax
10

S
Shinya Maeda 已提交
11
  it_behaves_like 'having unique enum values'
S
Shinya Maeda 已提交
12

13
  describe 'associations' do
14 15 16
    it { is_expected.to belong_to(:group) }
    it { is_expected.to belong_to(:namespace) }
    it { is_expected.to belong_to(:creator).class_name('User') }
17
    it { is_expected.to belong_to(:pool_repository) }
18
    it { is_expected.to have_many(:users) }
U
ubudzisz 已提交
19
    it { is_expected.to have_many(:services) }
20 21 22 23
    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) }
24
    it { is_expected.to have_many(:iterations) }
25
    it { is_expected.to have_many(:project_members).dependent(:delete_all) }
26
    it { is_expected.to have_many(:users).through(:project_members) }
27 28 29 30
    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) }
31
    it { is_expected.to have_many(:deploy_keys) }
32 33 34 35 36
    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) }
37
    it { is_expected.to have_one(:hangouts_chat_service) }
38
    it { is_expected.to have_one(:unify_circuit_service) }
39
    it { is_expected.to have_one(:webex_teams_service) }
M
Matt Coleman 已提交
40
    it { is_expected.to have_one(:packagist_service) }
41 42 43 44
    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) }
B
blackst0ne 已提交
45
    it { is_expected.to have_one(:discord_service) }
46 47 48 49 50
    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) }
51
    it { is_expected.to have_one(:hipchat_service) }
52 53 54 55 56 57 58 59 60
    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) }
61
    it { is_expected.to have_one(:youtrack_service) }
62 63 64 65 66
    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) }
67
    it { is_expected.to have_one(:project_repository) }
68
    it { is_expected.to have_one(:container_expiration_policy) }
69 70
    it { is_expected.to have_one(:statistics).class_name('ProjectStatistics') }
    it { is_expected.to have_one(:import_data).class_name('ProjectImportData') }
U
ubudzisz 已提交
71
    it { is_expected.to have_one(:last_event).class_name('Event') }
72
    it { is_expected.to have_one(:forked_from_project).through(:fork_network_member) }
Z
Zeger-Jan van de Weg 已提交
73
    it { is_expected.to have_one(:auto_devops).class_name('ProjectAutoDevops') }
74
    it { is_expected.to have_one(:error_tracking_setting).class_name('ErrorTracking::ProjectErrorTrackingSetting') }
75
    it { is_expected.to have_one(:project_setting) }
76
    it { is_expected.to have_one(:alerting_setting).class_name('Alerting::ProjectAlertingSetting') }
K
Kamil Trzcinski 已提交
77
    it { is_expected.to have_many(:commit_statuses) }
78
    it { is_expected.to have_many(:ci_pipelines) }
79
    it { is_expected.to have_many(:ci_refs) }
80
    it { is_expected.to have_many(:builds) }
81
    it { is_expected.to have_many(:build_trace_section_names)}
82
    it { is_expected.to have_many(:build_report_results) }
83 84 85 86
    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) }
K
Kamil Trzcinski 已提交
87
    it { is_expected.to have_many(:pages_domains) }
88 89
    it { is_expected.to have_many(:labels).class_name('ProjectLabel') }
    it { is_expected.to have_many(:users_star_projects) }
90
    it { is_expected.to have_many(:repository_languages) }
91 92 93 94 95 96 97
    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) }
98 99
    it { is_expected.to have_many(:forked_to_members).class_name('ForkNetworkMember') }
    it { is_expected.to have_many(:forks).through(:forked_to_members) }
J
Jan Provaznik 已提交
100
    it { is_expected.to have_many(:uploads) }
101
    it { is_expected.to have_many(:pipeline_schedules) }
102
    it { is_expected.to have_many(:members_and_requesters) }
M
Matija Čupić 已提交
103
    it { is_expected.to have_many(:clusters) }
104
    it { is_expected.to have_many(:management_clusters).class_name('Clusters::Cluster') }
105
    it { is_expected.to have_many(:kubernetes_namespaces) }
106
    it { is_expected.to have_many(:custom_attributes).class_name('ProjectCustomAttribute') }
107
    it { is_expected.to have_many(:project_badges).class_name('ProjectBadge') }
108
    it { is_expected.to have_many(:lfs_file_locks) }
M
Mayra Cabrera 已提交
109 110
    it { is_expected.to have_many(:project_deploy_tokens) }
    it { is_expected.to have_many(:deploy_tokens).through(:project_deploy_tokens) }
111
    it { is_expected.to have_many(:cycle_analytics_stages) }
112
    it { is_expected.to have_many(:external_pull_requests) }
113 114
    it { is_expected.to have_many(:sourced_pipelines) }
    it { is_expected.to have_many(:source_pipelines) }
115 116
    it { is_expected.to have_many(:prometheus_alert_events) }
    it { is_expected.to have_many(:self_managed_prometheus_alert_events) }
117
    it { is_expected.to have_many(:alert_management_alerts) }
118
    it { is_expected.to have_many(:jira_imports) }
119
    it { is_expected.to have_many(:metrics_users_starred_dashboards).inverse_of(:project) }
120
    it { is_expected.to have_many(:repository_storage_moves) }
121
    it { is_expected.to have_many(:reviews).inverse_of(:project) }
122

123 124 125 126 127 128
    it_behaves_like 'model with repository' do
      let_it_be(:container) { create(:project, :repository, path: 'somewhere') }
      let(:stubbed_container) { build_stubbed(:project) }
      let(:expected_full_path) { "#{container.namespace.full_path}/somewhere" }
    end

129 130 131 132 133
    it_behaves_like 'model with wiki' do
      let(:container) { create(:project, :wiki_repo) }
      let(:container_without_wiki) { create(:project) }
    end

134 135 136 137
    it 'has an inverse relationship with merge requests' do
      expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project)
    end

138 139 140 141 142 143 144 145 146 147 148 149 150 151
    it 'has a distinct has_many :lfs_objects relation through lfs_objects_projects' do
      project = create(:project)
      lfs_object = create(:lfs_object)
      [:project, :design].each do |repository_type|
        create(:lfs_objects_project, project: project,
                                     lfs_object: lfs_object,
                                     repository_type: repository_type)
      end

      expect(project.lfs_objects_projects.size).to eq(2)
      expect(project.lfs_objects.size).to eq(1)
      expect(project.lfs_objects.to_a).to eql([lfs_object])
    end

F
Felipe Artur 已提交
152 153
    context 'after initialized' do
      it "has a project_feature" do
154
        expect(described_class.new.project_feature).to be_present
155 156 157
      end
    end

158
    context 'when creating a new project' do
159
      let_it_be(:project) { create(:project) }
160

161
      it 'automatically creates a CI/CD settings row' do
162 163 164
        expect(project.ci_cd_settings).to be_an_instance_of(ProjectCiCdSetting)
        expect(project.ci_cd_settings).to be_persisted
      end
165

166 167 168 169 170
      it 'automatically creates a container expiration policy row' do
        expect(project.container_expiration_policy).to be_an_instance_of(ContainerExpirationPolicy)
        expect(project.container_expiration_policy).to be_persisted
      end

171 172 173 174 175 176 177 178 179 180
      it 'does not create another container expiration policy if there is already one' do
        project = build(:project)

        expect do
          container_expiration_policy = create(:container_expiration_policy, project: project)

          expect(project.container_expiration_policy).to eq(container_expiration_policy)
        end.to change { ContainerExpirationPolicy.count }.by(1)
      end

181 182 183 184
      it 'automatically creates a Pages metadata row' do
        expect(project.pages_metadatum).to be_an_instance_of(ProjectPagesMetadatum)
        expect(project.pages_metadatum).to be_persisted
      end
185

186
      it 'automatically builds a project setting row' do
187
        expect(project.project_setting).to be_an_instance_of(ProjectSetting)
188
        expect(project.project_setting).to be_new_record
189
      end
190 191
    end

192 193 194 195
    context 'updating cd_cd_settings' do
      it 'does not raise an error' do
        project = create(:project)

J
James Lopez 已提交
196
        expect { project.update(ci_cd_settings: nil) }.not_to raise_exception
197 198 199
      end
    end

200
    describe '#members & #requesters' do
201
      let(:project) { create(:project, :public) }
202 203
      let(:requester) { create(:user) }
      let(:developer) { create(:user) }
204

205 206
      before do
        project.request_access(requester)
207
        project.add_developer(developer)
208 209
      end

210 211
      it_behaves_like 'members and requesters associations' do
        let(:namespace) { project }
212 213
      end
    end
214

215
    describe 'ci_pipelines association' do
216 217
      it 'returns only pipelines from ci_sources' do
        expect(Ci::Pipeline).to receive(:ci_sources).and_call_original
218

219
        subject.ci_pipelines
220 221
      end
    end
G
gitlabhq 已提交
222 223
  end

224 225 226 227 228 229 230 231
  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) }
232 233
  end

234
  describe 'validation' do
235
    let!(:project) { create(:project) }
236

237 238
    it { is_expected.to validate_presence_of(:name) }
    it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) }
239
    it { is_expected.to validate_length_of(:name).is_at_most(255) }
240
    it { is_expected.to validate_presence_of(:path) }
241 242
    it { is_expected.to validate_length_of(:path).is_at_most(255) }
    it { is_expected.to validate_length_of(:description).is_at_most(2000) }
243 244 245
    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) }
246
    it { is_expected.not_to allow_value('/test/foo').for(:ci_config_path) }
247 248
    it { is_expected.to validate_presence_of(:creator) }
    it { is_expected.to validate_presence_of(:namespace) }
249
    it { is_expected.to validate_presence_of(:repository_storage) }
250
    it { is_expected.to validate_numericality_of(:max_artifacts_size).only_integer.is_greater_than(0) }
251

252 253 254 255 256
    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)
257
        .with_message('needs to be between 10 minutes and 1 month')
258 259
    end

260
    it 'does not allow new projects beyond user limits' do
261
      project2 = build(:project)
262 263 264 265 266 267 268

      allow(project2)
        .to receive(:creator)
        .and_return(
          double(can_create_project?: false, projects_limit: 0).as_null_object
        )

269
      expect(project2).not_to be_valid
270
    end
271

272 273 274 275 276 277 278
    it 'validates the visibility' do
      expect_any_instance_of(described_class).to receive(:visibility_level_allowed_as_fork).and_call_original
      expect_any_instance_of(described_class).to receive(:visibility_level_allowed_by_group).and_call_original

      create(:project)
    end

279
    context 'repository storages inclusion' do
280
      let(:project2) { build(:project, repository_storage: 'missing') }
281 282

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

287
      it "does not allow repository storages that don't match a label in the configuration" do
288 289 290 291
        expect(project2).not_to be_valid
        expect(project2.errors[:repository_storage].first).to match(/is not included in the list/)
      end
    end
292

293 294 295 296 297 298
    it 'validates presence of project_feature' do
      project = build(:project, project_feature: nil)

      expect(project).not_to be_valid
    end

299 300 301
    describe 'import_url' do
      it 'does not allow an invalid URI as import_url' do
        project = build(:project, import_url: 'invalid://')
J
James Lopez 已提交
302

303 304
        expect(project).not_to be_valid
      end
305

306 307 308
      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'
309

310 311
        expect(project).to be_valid
      end
312

313 314
      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')
315

316 317
        expect(project).not_to be_valid
      end
J
James Lopez 已提交
318

319 320
      it 'does allow a valid URI as import_url' do
        project = build(:project, import_url: 'http://gitlab.com/project.git')
J
James Lopez 已提交
321

322 323
        expect(project).to be_valid
      end
324

325 326
      it 'allows an empty URI' do
        project = build(:project, import_url: '')
327

328 329
        expect(project).to be_valid
      end
330

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

334 335
        expect(project.import_data).to be_nil
      end
336

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

340 341
        expect(project.import_data).to be_nil
      end
342

343 344
      it "does not allow import_url pointing to localhost" do
        project = build(:project, import_url: 'http://localhost:9000/t.git')
345

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

350 351 352 353 354 355 356
      it 'does not allow import_url pointing to the local network' do
        project = build(:project, import_url: 'https://192.168.1.1')

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

357 358
      it "does not allow import_url with invalid ports for new projects" do
        project = build(:project, import_url: 'http://github.com:25/t.git')
D
Douwe Maan 已提交
359

360 361 362
        expect(project).to be_invalid
        expect(project.errors[:import_url].first).to include('Only allowed ports are 80, 443')
      end
D
Douwe Maan 已提交
363

364 365 366
      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'
D
Douwe Maan 已提交
367

368 369 370
        expect(project).to be_invalid
        expect(project.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
      end
D
Douwe Maan 已提交
371

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

375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
        expect(project).to be_invalid
        expect(project.errors[:import_url].first).to include('Username needs to start with an alphanumeric character')
      end

      include_context 'invalid urls'

      it 'does not allow urls with CR or LF characters' do
        project = build(:project)

        aggregate_failures do
          urls_with_CRLF.each do |url|
            project.import_url = url

            expect(project).not_to be_valid
            expect(project.errors.full_messages.first).to match(/is blocked: URI is invalid/)
          end
        end
      end
393 394
    end

395 396
    describe 'project pending deletion' do
      let!(:project_pending_deletion) do
397
        create(:project,
398 399 400
               pending_delete: true)
      end
      let(:new_project) do
401
        build(:project,
402 403 404 405 406 407 408 409 410
              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
411
        expect(new_project.errors.full_messages.first).to eq(_('The project is still being deleted. Please try again later.'))
412 413
      end
    end
414 415 416

    describe 'path validation' do
      it 'allows paths reserved on the root namespace' do
417
        project = build(:project, path: 'api')
418 419 420 421 422

        expect(project).to be_valid
      end

      it 'rejects paths reserved on another level' do
423
        project = build(:project, path: 'tree')
424 425 426

        expect(project).not_to be_valid
      end
427 428 429

      it 'rejects nested paths' do
        parent = create(:group, :nested, path: 'environments')
430
        project = build(:project, path: 'folders', namespace: parent)
431 432 433

        expect(project).not_to be_valid
      end
434 435 436

      it 'allows a reserved group name' do
        parent = create(:group)
437
        project = build(:project, path: 'avatar', namespace: parent)
438 439 440

        expect(project).to be_valid
      end
441 442 443 444 445 446

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

        expect(project).to be_valid
      end
447
    end
G
gitlabhq 已提交
448
  end
449

450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
  describe '#all_pipelines' do
    let(:project) { create(:project) }

    before do
      create(:ci_pipeline, project: project, ref: 'master', source: :web)
      create(:ci_pipeline, project: project, ref: 'master', source: :external)
    end

    it 'has all pipelines' do
      expect(project.all_pipelines.size).to eq(2)
    end

    context 'when builds are disabled' do
      before do
        project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
      end

467
      it 'returns .external pipelines' do
468 469 470 471 472 473
        expect(project.all_pipelines).to all(have_attributes(source: 'external'))
        expect(project.all_pipelines.size).to eq(1)
      end
    end
  end

474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
  describe '#ci_pipelines' do
    let(:project) { create(:project) }

    before do
      create(:ci_pipeline, project: project, ref: 'master', source: :web)
      create(:ci_pipeline, project: project, ref: 'master', source: :external)
    end

    it 'has ci pipelines' do
      expect(project.ci_pipelines.size).to eq(2)
    end

    context 'when builds are disabled' do
      before do
        project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
      end

491
      it 'returns .external pipelines' do
492 493 494 495 496 497
        expect(project.ci_pipelines).to all(have_attributes(source: 'external'))
        expect(project.ci_pipelines.size).to eq(1)
      end
    end
  end

498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
  describe '#autoclose_referenced_issues' do
    context 'when DB entry is nil' do
      let(:project) { create(:project, autoclose_referenced_issues: nil) }

      it 'returns true' do
        expect(project.autoclose_referenced_issues).to be_truthy
      end
    end

    context 'when DB entry is true' do
      let(:project) { create(:project, autoclose_referenced_issues: true) }

      it 'returns true' do
        expect(project.autoclose_referenced_issues).to be_truthy
      end
    end

    context 'when DB entry is false' do
      let(:project) { create(:project, autoclose_referenced_issues: false) }

      it 'returns false' do
        expect(project.autoclose_referenced_issues).to be_falsey
      end
    end
  end

K
Kamil Trzcinski 已提交
524
  describe 'project token' do
525
    it 'sets an random token if none provided' do
526
      project = FactoryBot.create(:project, runners_token: '')
K
Kamil Trzcinski 已提交
527
      expect(project.runners_token).not_to eq('')
K
Kamil Trzcinski 已提交
528 529
    end

U
ubudzisz 已提交
530
    it 'does not set an random token if one provided' do
531
      project = FactoryBot.create(:project, runners_token: 'my-token')
K
Kamil Trzcinski 已提交
532
      expect(project.runners_token).to eq('my-token')
K
Kamil Trzcinski 已提交
533 534
    end
  end
G
gitlabhq 已提交
535

536
  describe 'Respond to' do
537 538 539 540
    it { is_expected.to respond_to(:url_to_repo) }
    it { is_expected.to respond_to(:execute_hooks) }
    it { is_expected.to respond_to(:owner) }
    it { is_expected.to respond_to(:path_with_namespace) }
541
    it { is_expected.to respond_to(:full_path) }
G
gitlabhq 已提交
542 543
  end

544
  describe 'delegation' do
545
    [:add_guest, :add_reporter, :add_developer, :add_maintainer, :add_user, :add_users].each do |method|
546 547 548 549 550
      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) }
551
    it { is_expected.to delegate_method(:root_ancestor).to(:namespace).with_arguments(allow_nil: true) }
552
    it { is_expected.to delegate_method(:last_pipeline).to(:commit).with_arguments(allow_nil: true) }
553 554
  end

555 556 557 558 559 560 561
  describe 'reference methods' do
    let_it_be(:owner)     { create(:user, name: 'Gitlab') }
    let_it_be(:namespace) { create(:namespace, name: 'Sample namespace', path: 'sample-namespace', owner: owner) }
    let_it_be(:project)   { create(:project, name: 'Sample project', path: 'sample-project', namespace: namespace) }
    let_it_be(:group)     { create(:group, name: 'Group', path: 'sample-group') }
    let_it_be(:another_project) { create(:project, namespace: namespace) }
    let_it_be(:another_namespace_project) { create(:project, name: 'another-project') }
562

563 564 565 566
    describe '#to_reference' do
      it 'returns the path with reference_postfix' do
        expect(project.to_reference).to eq("#{project.full_path}>")
      end
567

568 569
      it 'returns the path with reference_postfix when arg is self' do
        expect(project.to_reference(project)).to eq("#{project.full_path}>")
570 571
      end

572 573
      it 'returns the full_path with reference_postfix when full' do
        expect(project.to_reference(full: true)).to eq("#{project.full_path}>")
574 575
      end

576 577
      it 'returns the full_path with reference_postfix when cross-project' do
        expect(project.to_reference(build_stubbed(:project))).to eq("#{project.full_path}>")
578 579 580
      end
    end

581 582 583 584 585
    describe '#to_reference_base' do
      context 'when nil argument' do
        it 'returns nil' do
          expect(project.to_reference_base).to be_nil
        end
586 587
      end

588 589 590
      context 'when full is true' do
        it 'returns complete path to the project', :aggregate_failures do
          be_full_path = eq('sample-namespace/sample-project')
591

592 593 594 595
          expect(project.to_reference_base(full: true)).to be_full_path
          expect(project.to_reference_base(project, full: true)).to be_full_path
          expect(project.to_reference_base(group, full: true)).to be_full_path
        end
596
      end
597

598 599 600 601 602
      context 'when same project argument' do
        it 'returns nil' do
          expect(project.to_reference_base(project)).to be_nil
        end
      end
603

604 605 606 607
      context 'when cross namespace project argument' do
        it 'returns complete path to the project' do
          expect(project.to_reference_base(another_namespace_project)).to eq 'sample-namespace/sample-project'
        end
608 609
      end

610
      context 'when same namespace / cross-project argument' do
611
        it 'returns path to the project' do
612
          expect(project.to_reference_base(another_project)).to eq 'sample-project'
613 614 615
        end
      end

616 617 618 619
      context 'when different namespace / cross-project argument with same owner' do
        let(:another_namespace_same_owner) { create(:namespace, path: 'another-namespace', owner: owner) }
        let(:another_project_same_owner)   { create(:project, path: 'another-project', namespace: another_namespace_same_owner) }

620
        it 'returns full path to the project' do
621
          expect(project.to_reference_base(another_project_same_owner)).to eq 'sample-namespace/sample-project'
622
        end
623
      end
624

625 626 627 628 629 630
      context 'when argument is a namespace' do
        context 'with same project path' do
          it 'returns path to the project' do
            expect(project.to_reference_base(namespace)).to eq 'sample-project'
          end
        end
631

632 633 634 635 636
        context 'with different project path' do
          it 'returns full path to the project' do
            expect(project.to_reference_base(group)).to eq 'sample-namespace/sample-project'
          end
        end
637 638 639
      end
    end

640 641 642 643 644
    describe '#to_human_reference' do
      context 'when nil argument' do
        it 'returns nil' do
          expect(project.to_human_reference).to be_nil
        end
645 646
      end

647 648 649 650
      context 'when same project argument' do
        it 'returns nil' do
          expect(project.to_human_reference(project)).to be_nil
        end
651 652
      end

653 654 655 656 657
      context 'when cross namespace project argument' do
        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
658

659 660 661 662
      context 'when same namespace / cross-project argument' do
        it 'returns name of the project' do
          expect(project.to_human_reference(another_project)).to eq 'Sample project'
        end
663
      end
664 665 666
    end
  end

V
Valery Sizov 已提交
667
  describe '#merge_method' do
668 669 670 671 672 673 674
    using RSpec::Parameterized::TableSyntax

    where(:ff, :rebase, :method) do
      true  | true  | :ff
      true  | false | :ff
      false | true  | :rebase_merge
      false | false | :merge
V
Valery Sizov 已提交
675 676
    end

677 678 679 680 681 682
    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) }
V
Valery Sizov 已提交
683 684 685
    end
  end

686
  it 'returns valid url to repo' do
687
    project = described_class.new(path: 'somewhere')
688
    expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
G
gitlabhq 已提交
689 690
  end

I
Imre Farkas 已提交
691 692
  describe "#readme_url" do
    context 'with a non-existing repository' do
693
      let(:project) { create(:project) }
I
Imre Farkas 已提交
694

695
      it 'returns nil' do
I
Imre Farkas 已提交
696 697 698 699 700 701
        expect(project.readme_url).to be_nil
      end
    end

    context 'with an existing repository' do
      context 'when no README exists' do
702
        let(:project) { create(:project, :empty_repo) }
I
Imre Farkas 已提交
703

704
        it 'returns nil' do
I
Imre Farkas 已提交
705 706 707 708 709
          expect(project.readme_url).to be_nil
        end
      end

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

I
Imre Farkas 已提交
712
        it 'returns the README' do
713
          expect(project.readme_url).to eq("#{project.web_url}/-/blob/master/README.md")
I
Imre Farkas 已提交
714 715 716 717 718
        end
      end
    end
  end

J
Jan Provaznik 已提交
719
  describe "#new_issuable_address" do
720
    let(:project) { create(:project, path: "somewhere") }
721 722
    let(:user) { create(:user) }

723 724 725 726 727 728
    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
729
        address = "p+#{project.full_path_slug}-#{project.project_id}-#{user.incoming_email_token}-issue@gl.ab"
730

J
Jan Provaznik 已提交
731 732 733 734
        expect(project.new_issuable_address(user, 'issue')).to eq(address)
      end

      it 'returns the address to create a new merge request' do
735
        address = "p+#{project.full_path_slug}-#{project.project_id}-#{user.incoming_email_token}-merge-request@gl.ab"
J
Jan Provaznik 已提交
736 737

        expect(project.new_issuable_address(user, 'merge_request')).to eq(address)
738
      end
739 740 741 742

      it 'returns nil with invalid address type' do
        expect(project.new_issuable_address(user, 'invalid_param')).to be_nil
      end
743 744 745 746 747 748
    end

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

750
      it 'returns nil' do
J
Jan Provaznik 已提交
751 752 753 754 755
        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
756
      end
757 758 759
    end
  end

760
  describe 'last_activity methods' do
S
Stan Hu 已提交
761 762
    let(:timestamp) { 2.hours.ago }
    # last_activity_at gets set to created_at upon creation
763
    let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) }
G
gitlabhq 已提交
764

765
    describe 'last_activity' do
766
      it 'alias last_activity to last_event' do
767
        last_event = create(:event, :closed, project: project)
768

769
        expect(project.last_activity).to eq(last_event)
770
      end
G
gitlabhq 已提交
771 772
    end

773 774
    describe 'last_activity_date' do
      it 'returns the creation date of the project\'s last event if present' do
775
        new_event = create(:event, :closed, project: project, created_at: Time.current)
776

S
Stan Hu 已提交
777
        project.reload
778
        expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i)
779
      end
780

781
      it 'returns the project\'s last update date if it has no events' do
782
        expect(project.last_activity_date).to eq(project.updated_at)
783
      end
784 785

      it 'returns the most recent timestamp' do
L
Lin Jen-Shin 已提交
786 787 788
        project.update(updated_at: nil,
                       last_activity_at: timestamp,
                       last_repository_updated_at: timestamp - 1.hour)
789

790
        expect(project.last_activity_date).to be_like_time(timestamp)
791

L
Lin Jen-Shin 已提交
792 793 794
        project.update(updated_at: timestamp,
                       last_activity_at: timestamp - 1.hour,
                       last_repository_updated_at: nil)
795

796
        expect(project.last_activity_date).to be_like_time(timestamp)
797
      end
798 799
    end
  end
800

801
  describe '#get_issue' do
802
    let(:project) { create(:project) }
S
Stan Hu 已提交
803
    let!(:issue)  { create(:issue, project: project) }
804 805 806
    let(:user)    { create(:user) }

    before do
807
      project.add_developer(user)
808
    end
809 810 811

    context 'with default issues tracker' do
      it 'returns an issue' do
812
        expect(project.get_issue(issue.iid, user)).to eq issue
813 814
      end

S
Stan Hu 已提交
815 816 817 818
      it 'returns count of open issues' do
        expect(project.open_issues_count).to eq(1)
      end

819
      it 'returns nil when no issue found' do
820
        expect(project.get_issue(non_existing_record_id, user)).to be_nil
821 822 823 824 825
      end

      it "returns nil when user doesn't have access" do
        user = create(:user)
        expect(project.get_issue(issue.iid, user)).to eq nil
826 827 828 829
      end
    end

    context 'with external issues tracker' do
830
      let!(:internal_issue) { create(:issue, project: project) }
831

832
      before do
833
        allow(project).to receive(:external_issue_tracker).and_return(true)
834 835
      end

836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872
      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
873 874 875 876 877
      end
    end
  end

  describe '#issue_exists?' do
878
    let(:project) { create(:project) }
879 880 881 882 883 884 885 886 887 888 889 890

    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

L
Lin Jen-Shin 已提交
891
  describe '#to_param' do
892 893
    context 'with namespace' do
      before do
894
        @group = create(:group, name: 'gitlab')
895
        @project = create(:project, name: 'gitlabhq', namespace: @group)
896 897
      end

V
Vinnie Okada 已提交
898
      it { expect(@project.to_param).to eq('gitlabhq') }
899
    end
900 901 902

    context 'with invalid path' do
      it 'returns previous path to keep project suitable for use in URLs when persisted' do
903
        project = create(:project, path: 'gitlab')
904 905 906 907 908 909 910
        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
911
        project = build(:project, path: 'gitlab')
912 913 914 915 916 917
        project.path = 'foo&bar'

        expect(project).not_to be_valid
        expect(project.to_param).to eq 'foo&bar'
      end
    end
918
  end
D
Dmitriy Zaporozhets 已提交
919

L
Lin Jen-Shin 已提交
920
  describe '#default_issues_tracker?' do
921
    it "is true if used internal tracker" do
922
      project = build(:project)
923

924
      expect(project.default_issues_tracker?).to be_truthy
925 926
    end

927
    it "is false if used other tracker" do
928 929 930 931
      # NOTE: The current nature of this factory requires persistence
      project = create(:redmine_project)

      expect(project.default_issues_tracker?).to be_falsey
932 933 934
    end
  end

L
Lin Jen-Shin 已提交
935
  describe '#external_issue_tracker' do
936
    let(:project) { create(:project) }
937 938 939
    let(:ext_project) { create(:redmine_project) }

    context 'on existing projects with no value for has_external_issue_tracker' do
940
      before do
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 966 967 968 969
        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

L
Lin Jen-Shin 已提交
970
  describe '#cache_has_external_issue_tracker' do
971
    let(:project) { create(:project, has_external_issue_tracker: nil) }
972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989

    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
T
Toon Claes 已提交
990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027

    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
1028 1029
  end

1030
  describe '#has_wiki?' do
1031 1032 1033
    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) }
1034 1035 1036 1037 1038 1039 1040 1041

    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

1042
  describe '#external_wiki' do
1043
    let(:project) { create(:project) }
1044

1045 1046 1047 1048 1049
    context 'with an active external wiki' do
      before do
        create(:service, project: project, type: 'ExternalWikiService', active: true)
        project.external_wiki
      end
1050

1051 1052 1053
      it 'sets :has_external_wiki as true' do
        expect(project.has_external_wiki).to be(true)
      end
1054

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

1058 1059 1060 1061
        project.services.external_wikis.first.destroy

        expect(project.has_external_wiki).to be(false)
      end
1062 1063
    end

1064 1065 1066 1067
    context 'with an inactive external wiki' do
      before do
        create(:service, project: project, type: 'ExternalWikiService', active: false)
      end
1068

1069 1070 1071
      it 'sets :has_external_wiki as false' do
        expect(project.has_external_wiki).to be(false)
      end
1072 1073
    end

1074 1075 1076 1077
    context 'with no external wiki' do
      before do
        project.external_wiki
      end
1078

1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
      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
1090 1091 1092
    end
  end

1093 1094
  describe '#star_count' do
    it 'counts stars from multiple users' do
1095 1096
      user1 = create(:user)
      user2 = create(:user)
1097
      project = create(:project, :public)
C
Ciro Santilli 已提交
1098 1099

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

C
Ciro Santilli 已提交
1101
      user1.toggle_star(project)
1102 1103
      expect(project.reload.star_count).to eq(1)

C
Ciro Santilli 已提交
1104
      user2.toggle_star(project)
1105 1106 1107
      project.reload
      expect(project.reload.star_count).to eq(2)

C
Ciro Santilli 已提交
1108
      user1.toggle_star(project)
1109 1110 1111
      project.reload
      expect(project.reload.star_count).to eq(1)

C
Ciro Santilli 已提交
1112
      user2.toggle_star(project)
1113 1114 1115 1116
      project.reload
      expect(project.reload.star_count).to eq(0)
    end

1117
    it 'counts stars on the right project' do
1118
      user = create(:user)
1119 1120
      project1 = create(:project, :public)
      project2 = create(:project, :public)
1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147

      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)
C
Ciro Santilli 已提交
1148 1149
    end
  end
1150

L
Lin Jen-Shin 已提交
1151
  describe '#avatar_type' do
1152
    let(:project) { create(:project) }
1153

1154
    it 'is true if avatar is image' do
1155
      project.update_attribute(:avatar, 'uploads/avatar.png')
1156
      expect(project.avatar_type).to be_truthy
1157 1158
    end

1159
    it 'is false if avatar is html page' do
1160
      project.update_attribute(:avatar, 'uploads/avatar.html')
1161
      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'])
1162 1163
    end
  end
S
sue445 已提交
1164

L
Lin Jen-Shin 已提交
1165
  describe '#avatar_url' do
S
sue445 已提交
1166 1167
    subject { project.avatar_url }

1168
    let(:project) { create(:project) }
S
sue445 已提交
1169

1170
    context 'when avatar file is uploaded' do
T
Tim Zallmann 已提交
1171
      let(:project) { create(:project, :public, :with_avatar) }
S
sue445 已提交
1172

1173
      it 'shows correct url' do
1174 1175
        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)
1176
      end
S
sue445 已提交
1177 1178
    end

1179
    context 'when avatar file in git' do
S
sue445 已提交
1180 1181 1182 1183
      before do
        allow(project).to receive(:avatar_in_git) { true }
      end

1184
      let(:avatar_path) { "/#{project.full_path}/-/avatar" }
S
sue445 已提交
1185

1186
      it { is_expected.to eq "http://#{Gitlab.config.gitlab.host}#{avatar_path}" }
S
sue445 已提交
1187
    end
1188 1189

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

1192
      it { is_expected.to eq nil }
1193
    end
S
sue445 已提交
1194
  end
1195

1196
  describe '#pipeline_for' do
1197
    let(:project) { create(:project, :repository) }
K
Kamil Trzcinski 已提交
1198

1199 1200
    shared_examples 'giving the correct pipeline' do
      it { is_expected.to eq(pipeline) }
K
Kamil Trzcinski 已提交
1201

1202
      context 'return latest' do
1203
        let!(:pipeline2) { create_pipeline(project) }
K
Kamil Trzcinski 已提交
1204

1205
        it { is_expected.to eq(pipeline2) }
K
Kamil Trzcinski 已提交
1206
      end
1207 1208
    end

M
Matija Čupić 已提交
1209 1210 1211 1212 1213
    context 'with a matching pipeline' do
      let!(:pipeline) { create_pipeline(project) }

      context 'with explicit sha' do
        subject { project.pipeline_for('master', pipeline.sha) }
1214

M
Matija Čupić 已提交
1215
        it_behaves_like 'giving the correct pipeline'
1216

M
Matija Čupić 已提交
1217 1218
        context 'with supplied id' do
          let!(:other_pipeline) { create_pipeline(project) }
1219

M
Matija Čupić 已提交
1220
          subject { project.pipeline_for('master', pipeline.sha, other_pipeline.id) }
1221

M
Matija Čupić 已提交
1222 1223 1224 1225 1226 1227 1228 1229
          it { is_expected.to eq(other_pipeline) }
        end
      end

      context 'with implicit sha' do
        subject { project.pipeline_for('master') }

        it_behaves_like 'giving the correct pipeline'
1230
      end
1231 1232
    end

M
Matija Čupić 已提交
1233
    context 'when there is no matching pipeline' do
1234 1235
      subject { project.pipeline_for('master') }

M
Matija Čupić 已提交
1236
      it { is_expected.to be_nil }
1237
    end
1238
  end
1239

1240 1241 1242 1243 1244
  describe '#pipelines_for' do
    let(:project) { create(:project, :repository) }
    let!(:pipeline) { create_pipeline(project) }
    let!(:other_pipeline) { create_pipeline(project) }

M
Matija Čupić 已提交
1245
    subject { project.pipelines_for(project.default_branch, project.commit.sha) }
1246

M
Matija Čupić 已提交
1247
    it { is_expected.to contain_exactly(pipeline, other_pipeline) }
1248 1249
  end

L
Lin Jen-Shin 已提交
1250
  describe '#builds_enabled' do
1251
    let(:project) { create(:project) }
1252

1253 1254 1255
    subject { project.builds_enabled }

    it { expect(project.builds_enabled?).to be_truthy }
1256
  end
Y
Yorick Peterse 已提交
1257

1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269
  describe '.sort_by_attribute' do
    it 'reorders the input relation by start count desc' do
      project1 = create(:project, star_count: 2)
      project2 = create(:project, star_count: 1)
      project3 = create(:project)

      projects = described_class.sort_by_attribute(:stars_desc)

      expect(projects).to eq([project1, project2, project3])
    end
  end

1270
  describe '.with_shared_runners' do
1271
    subject { described_class.with_shared_runners }
1272 1273

    context 'when shared runners are enabled for project' do
1274
      let!(:project) { create(:project, shared_runners_enabled: true) }
1275 1276 1277 1278 1279 1280 1281

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

    context 'when shared runners are disabled for project' do
1282
      let!(:project) { create(:project, shared_runners_enabled: false) }
1283 1284 1285 1286 1287 1288 1289

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

1290
  describe '.cached_count', :use_clean_rails_memory_store_caching do
1291
    let(:group)     { create(:group, :public) }
1292 1293
    let!(:project1) { create(:project, :public, group: group) }
    let!(:project2) { create(:project, :public, group: group) }
1294 1295

    it 'returns total project count' do
1296
      expect(described_class).to receive(:count).once.and_call_original
1297 1298

      3.times do
1299
        expect(described_class.cached_count).to eq(2)
1300 1301 1302 1303
      end
    end
  end

Y
Yorick Peterse 已提交
1304
  describe '.trending' do
F
Felipe Artur 已提交
1305
    let(:group)    { create(:group, :public) }
1306 1307
    let(:project1) { create(:project, :public, :repository, group: group) }
    let(:project2) { create(:project, :public, :repository, group: group) }
Y
Yorick Peterse 已提交
1308 1309

    before do
1310
      create_list(:note_on_commit, 2, project: project1)
Y
Yorick Peterse 已提交
1311 1312 1313

      create(:note_on_commit, project: project2)

Y
Yorick Peterse 已提交
1314
      TrendingProject.refresh!
Y
Yorick Peterse 已提交
1315 1316
    end

Y
Yorick Peterse 已提交
1317
    subject { described_class.trending.to_a }
Y
Yorick Peterse 已提交
1318

Y
Yorick Peterse 已提交
1319 1320
    it 'sorts projects by the amount of notes in descending order' do
      expect(subject).to eq([project1, project2])
Y
Yorick Peterse 已提交
1321
    end
1322 1323

    it 'does not take system notes into account' do
1324
      create_list(:note_on_commit, 10, project: project2, system: true)
1325 1326 1327

      expect(described_class.trending.to_a).to eq([project1, project2])
    end
Y
Yorick Peterse 已提交
1328
  end
Y
Yorick Peterse 已提交
1329

T
Toon Claes 已提交
1330 1331 1332 1333
  describe '.starred_by' do
    it 'returns only projects starred by the given user' do
      user1 = create(:user)
      user2 = create(:user)
1334 1335 1336
      project1 = create(:project)
      project2 = create(:project)
      create(:project)
T
Toon Claes 已提交
1337 1338 1339
      user1.toggle_star(project1)
      user2.toggle_star(project2)

1340
      expect(described_class.starred_by(user1)).to contain_exactly(project1)
T
Toon Claes 已提交
1341 1342 1343
    end
  end

1344 1345 1346 1347 1348 1349 1350 1351
  describe '.with_limit' do
    it 'limits the number of projects returned' do
      create_list(:project, 3)

      expect(described_class.with_limit(1).count).to eq(1)
    end
  end

Y
Yorick Peterse 已提交
1352
  describe '.visible_to_user' do
1353
    let!(:project) { create(:project, :private) }
Y
Yorick Peterse 已提交
1354 1355 1356 1357 1358 1359
    let!(:user)    { create(:user) }

    subject { described_class.visible_to_user(user) }

    describe 'when a user has access to a project' do
      before do
1360
        project.add_user(user, Gitlab::Access::MAINTAINER)
Y
Yorick Peterse 已提交
1361 1362 1363 1364 1365 1366 1367 1368 1369
      end

      it { is_expected.to eq([project]) }
    end

    describe 'when a user does not have access to any projects' do
      it { is_expected.to eq([]) }
    end
  end
K
Kamil Trzcinski 已提交
1370

1371 1372 1373 1374 1375 1376 1377 1378 1379 1380
  describe '.with_service' do
    before do
      create_list(:prometheus_project, 2)
    end

    it 'avoid n + 1' do
      expect { described_class.with_service(:prometheus_service).map(&:prometheus_service) }.not_to exceed_query_limit(1)
    end
  end

1381
  context 'repository storage by default' do
1382
    let(:project) { build(:project) }
1383

1384
    it 'picks storage from ApplicationSetting' do
1385 1386 1387 1388
      expect_next_instance_of(ApplicationSetting) do |instance|
        expect(instance).to receive(:pick_repository_storage).and_return('picked')
      end
      expect(described_class).to receive(:pick_repository_storage).and_call_original
1389 1390 1391

      expect(project.repository_storage).to eq('picked')
    end
1392 1393
  end

K
Kamil Trzcinski 已提交
1394
  context 'shared runners by default' do
1395
    let(:project) { create(:project) }
K
Kamil Trzcinski 已提交
1396 1397 1398 1399

    subject { project.shared_runners_enabled }

    context 'are enabled' do
1400 1401 1402
      before do
        stub_application_setting(shared_runners_enabled: true)
      end
K
Kamil Trzcinski 已提交
1403 1404 1405 1406 1407

      it { is_expected.to be_truthy }
    end

    context 'are disabled' do
1408 1409 1410
      before do
        stub_application_setting(shared_runners_enabled: false)
      end
K
Kamil Trzcinski 已提交
1411 1412 1413 1414 1415

      it { is_expected.to be_falsey }
    end
  end

1416
  describe '#any_runners?' do
1417
    context 'shared runners' do
1418
      let(:project) { create(:project, shared_runners_enabled: shared_runners_enabled) }
1419 1420
      let(:specific_runner) { create(:ci_runner, :project, projects: [project]) }
      let(:shared_runner) { create(:ci_runner, :instance) }
K
Kamil Trzcinski 已提交
1421

1422 1423
      context 'for shared runners disabled' do
        let(:shared_runners_enabled) { false }
1424

1425 1426 1427
        it 'has no runners available' do
          expect(project.any_runners?).to be_falsey
        end
1428

1429
        it 'has a specific runner' do
1430
          specific_runner
1431

1432 1433 1434 1435 1436
          expect(project.any_runners?).to be_truthy
        end

        it 'has a shared runner, but they are prohibited to use' do
          shared_runner
1437

1438 1439
          expect(project.any_runners?).to be_falsey
        end
1440

1441
        it 'checks the presence of specific runner' do
1442
          specific_runner
1443

1444 1445
          expect(project.any_runners? { |runner| runner == specific_runner }).to be_truthy
        end
1446 1447

        it 'returns false if match cannot be found' do
1448
          specific_runner
1449

1450 1451
          expect(project.any_runners? { false }).to be_falsey
        end
K
Kamil Trzcinski 已提交
1452
      end
1453

1454 1455 1456 1457 1458
      context 'for shared runners enabled' do
        let(:shared_runners_enabled) { true }

        it 'has a shared runner' do
          shared_runner
1459

1460 1461 1462 1463 1464
          expect(project.any_runners?).to be_truthy
        end

        it 'checks the presence of shared runner' do
          shared_runner
1465

1466 1467
          expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy
        end
1468 1469 1470

        it 'returns false if match cannot be found' do
          shared_runner
1471

1472 1473
          expect(project.any_runners? { false }).to be_falsey
        end
K
Kamil Trzcinski 已提交
1474 1475
      end
    end
1476

1477
    context 'group runners' do
1478 1479
      let(:project) { create(:project, group_runners_enabled: group_runners_enabled) }
      let(:group) { create(:group, projects: [project]) }
1480
      let(:group_runner) { create(:ci_runner, :group, groups: [group]) }
1481 1482 1483

      context 'for group runners disabled' do
        let(:group_runners_enabled) { false }
1484

1485 1486 1487 1488 1489 1490
        it 'has no runners available' do
          expect(project.any_runners?).to be_falsey
        end

        it 'has a group runner, but they are prohibited to use' do
          group_runner
1491

1492 1493
          expect(project.any_runners?).to be_falsey
        end
K
Kamil Trzcinski 已提交
1494 1495
      end

1496 1497 1498 1499 1500
      context 'for group runners enabled' do
        let(:group_runners_enabled) { true }

        it 'has a group runner' do
          group_runner
1501

1502 1503 1504 1505 1506
          expect(project.any_runners?).to be_truthy
        end

        it 'checks the presence of group runner' do
          group_runner
1507

1508 1509
          expect(project.any_runners? { |runner| runner == group_runner }).to be_truthy
        end
1510 1511 1512

        it 'returns false if match cannot be found' do
          group_runner
1513

1514 1515
          expect(project.any_runners? { false }).to be_falsey
        end
K
Kamil Trzcinski 已提交
1516 1517 1518
      end
    end
  end
1519

1520
  describe '#shared_runners' do
1521
    let!(:runner) { create(:ci_runner, :instance) }
1522 1523 1524 1525

    subject { project.shared_runners }

    context 'when shared runners are enabled for project' do
1526
      let!(:project) { create(:project, shared_runners_enabled: true) }
1527 1528 1529 1530 1531 1532 1533

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

    context 'when shared runners are disabled for project' do
1534
      let!(:project) { create(:project, shared_runners_enabled: false) }
1535 1536 1537 1538 1539 1540 1541

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

1542 1543 1544 1545 1546 1547 1548 1549 1550 1551
  describe '#visibility_level' do
    let(:project) { build(:project) }

    subject { project.visibility_level }

    context 'by default' do
      it { is_expected.to eq(Gitlab::VisibilityLevel::PRIVATE) }
    end

    context 'when set to INTERNAL in application settings' do
1552 1553
      using RSpec::Parameterized::TableSyntax

1554 1555 1556 1557 1558
      before do
        stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
      end

      it { is_expected.to eq(Gitlab::VisibilityLevel::INTERNAL) }
1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573

      where(:attribute_name, :value) do
        :visibility | 'public'
        :visibility_level | Gitlab::VisibilityLevel::PUBLIC
        'visibility' | 'public'
        'visibility_level' | Gitlab::VisibilityLevel::PUBLIC
      end

      with_them do
        it 'sets the visibility level' do
          proj = described_class.new(attribute_name => value, name: 'test', path: 'test')

          expect(proj.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
        end
      end
1574 1575 1576
    end
  end

1577
  describe '#visibility_level_allowed?' do
1578
    let(:project) { create(:project, :internal) }
1579 1580 1581 1582 1583 1584 1585 1586

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

    context 'when checking on forked project' do
1587
      let(:project)        { create(:project, :internal) }
1588
      let(:forked_project) { fork_project(project) }
1589 1590 1591 1592 1593

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

1596
  describe '#pages_deployed?' do
1597
    let(:project) { create(:project) }
1598 1599 1600 1601

    subject { project.pages_deployed? }

    context 'if public folder does exist' do
1602 1603 1604
      before do
        allow(Dir).to receive(:exist?).with(project.public_pages_path).and_return(true)
      end
1605 1606 1607 1608 1609 1610 1611 1612 1613

      it { is_expected.to be_truthy }
    end

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

1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636
  describe '#default_branch_protected?' do
    using RSpec::Parameterized::TableSyntax

    let_it_be(:project) { create(:project) }

    subject { project.default_branch_protected? }

    where(:default_branch_protection_level, :result) do
      Gitlab::Access::PROTECTION_NONE           | false
      Gitlab::Access::PROTECTION_DEV_CAN_PUSH   | false
      Gitlab::Access::PROTECTION_DEV_CAN_MERGE  | true
      Gitlab::Access::PROTECTION_FULL           | true
    end

    with_them do
      before do
        expect(project.namespace).to receive(:default_branch_protection).and_return(default_branch_protection_level)
      end

      it { is_expected.to eq(result) }
    end
  end

1637
  describe '#pages_url' do
1638 1639
    let(:group) { create(:group, name: group_name) }
    let(:project) { create(:project, namespace: group, name: project_name) }
1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660
    let(:domain) { 'Example.com' }

    subject { project.pages_url }

    before do
      allow(Settings.pages).to receive(:host).and_return(domain)
      allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com')
    end

    context 'group page' do
      let(:group_name) { 'Group' }
      let(:project_name) { 'group.example.com' }

      it { is_expected.to eq("http://group.example.com") }
    end

    context 'project page' do
      let(:group_name) { 'Group' }
      let(:project_name) { 'Project' }

      it { is_expected.to eq("http://group.example.com/project") }
1661 1662 1663 1664
    end
  end

  describe '#pages_group_url' do
1665 1666
    let(:group) { create(:group, name: group_name) }
    let(:project) { create(:project, namespace: group, name: project_name) }
1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688
    let(:domain) { 'Example.com' }
    let(:port) { 1234 }

    subject { project.pages_group_url }

    before do
      allow(Settings.pages).to receive(:host).and_return(domain)
      allow(Gitlab.config.pages).to receive(:url).and_return("http://example.com:#{port}")
    end

    context 'group page' do
      let(:group_name) { 'Group' }
      let(:project_name) { 'group.example.com' }

      it { is_expected.to eq("http://group.example.com:#{port}") }
    end

    context 'project page' do
      let(:group_name) { 'Group' }
      let(:project_name) { 'Project' }

      it { is_expected.to eq("http://group.example.com:#{port}") }
1689 1690 1691
    end
  end

1692
  describe '.search' do
1693
    let_it_be(:project) { create(:project, description: 'kitten mittens') }
1694

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

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

1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730
    it 'returns projects with a matching name regardless of the casing' do
      expect(described_class.search(project.name.upcase)).to eq([project])
    end

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

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

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

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

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

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

1731
    context 'when include_namespace is true' do
1732 1733 1734
      let_it_be(:group) { create(:group) }
      let_it_be(:project) { create(:project, group: group) }

1735 1736
      it 'returns projects that match the group path' do
        expect(described_class.search(group.path, include_namespace: true)).to eq([project])
1737 1738
      end

1739 1740
      it 'returns projects that match the full path' do
        expect(described_class.search(project.full_path, include_namespace: true)).to eq([project])
1741 1742 1743
      end
    end

1744
    describe 'with pending_delete project' do
1745
      let(:pending_delete_project) { create(:project, pending_delete: true) }
1746 1747 1748 1749 1750 1751 1752

      it 'shows pending deletion project' do
        search_result = described_class.search(pending_delete_project.name)

        expect(search_result).to eq([pending_delete_project])
      end
    end
1753
  end
1754

Y
Yorick Peterse 已提交
1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770
  describe '.optionally_search' do
    let(:project) { create(:project) }

    it 'searches for projects matching the query if one is given' do
      relation = described_class.optionally_search(project.name)

      expect(relation).to eq([project])
    end

    it 'returns the current relation if no search query is given' do
      relation = described_class.where(id: project.id)

      expect(relation.optionally_search).to eq(relation)
    end
  end

1771
  describe '.eager_load_namespace_and_owner' do
Y
Yorick Peterse 已提交
1772 1773 1774
    it 'eager loads the namespace and namespace owner' do
      create(:project)

1775
      row = described_class.eager_load_namespace_and_owner.first
Y
Yorick Peterse 已提交
1776 1777 1778 1779 1780 1781
      recorder = ActiveRecord::QueryRecorder.new { row.namespace.owner }

      expect(recorder.count).to be_zero
    end
  end

1782
  describe '#expire_caches_before_rename' do
1783
    let(:project) { create(:project, :repository) }
1784 1785
    let(:repo)    { double(:repo, exists?: true) }
    let(:wiki)    { double(:wiki, exists?: true) }
1786
    let(:design)  { double(:design, exists?: true) }
1787 1788

    it 'expires the caches of the repository and wiki' do
1789 1790 1791
      # In EE, there are design repositories as well
      allow(Repository).to receive(:new).and_call_original

1792
      allow(Repository).to receive(:new)
1793
        .with('foo', project, shard: project.repository_storage)
1794
        .and_return(repo)
1795

1796
      allow(Repository).to receive(:new)
1797
        .with('foo.wiki', project, shard: project.repository_storage, repo_type: Gitlab::GlRepository::WIKI)
1798
        .and_return(wiki)
1799

1800 1801 1802 1803
      allow(Repository).to receive(:new)
        .with('foo.design', project, shard: project.repository_storage, repo_type: Gitlab::GlRepository::DESIGN)
        .and_return(design)

1804 1805
      expect(repo).to receive(:before_delete)
      expect(wiki).to receive(:before_delete)
1806
      expect(design).to receive(:before_delete)
1807 1808 1809 1810

      project.expire_caches_before_rename('foo')
    end
  end
1811 1812

  describe '.search_by_title' do
1813
    let(:project) { create(:project, name: 'kittens') }
1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826

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

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

    it 'returns projects with a matching name regardless of the casing' do
      expect(described_class.search_by_title('KITTENS')).to eq([project])
    end
  end
1827 1828 1829 1830 1831

  context 'when checking projects from groups' do
    let(:private_group)    { create(:group, visibility_level: 0)  }
    let(:internal_group)   { create(:group, visibility_level: 10) }

1832 1833
    let(:private_project)  { create(:project, :private, group: private_group) }
    let(:internal_project) { create(:project, :internal, group: internal_group) }
1834 1835 1836 1837 1838 1839 1840 1841 1842

    context 'when group is private project can not be internal' do
      it { expect(private_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_falsey }
    end

    context 'when group is internal project can not be public' do
      it { expect(internal_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey }
    end
  end
1843

1844
  describe '#track_project_repository' do
1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859
    shared_examples 'tracks storage location' do
      context 'when a project repository entry does not exist' do
        it 'creates a new entry' do
          expect { project.track_project_repository }.to change(project, :project_repository)
        end

        it 'tracks the project storage location' do
          project.track_project_repository

          expect(project.project_repository).to have_attributes(
            disk_path: project.disk_path,
            shard_name: project.repository_storage
          )
        end
      end
1860

1861 1862 1863 1864 1865 1866 1867
      context 'when a tracking entry exists' do
        let!(:project_repository) { create(:project_repository, project: project) }
        let!(:shard) { create(:shard, name: 'foo') }

        it 'does not create a new entry in the database' do
          expect { project.track_project_repository }.not_to change(project, :project_repository)
        end
1868

1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880
        it 'updates the project storage location' do
          allow(project).to receive(:disk_path).and_return('fancy/new/path')
          allow(project).to receive(:repository_storage).and_return('foo')

          project.track_project_repository

          expect(project.project_repository).to have_attributes(
            disk_path: 'fancy/new/path',
            shard_name: 'foo'
          )
        end
      end
1881 1882
    end

1883 1884
    context 'with projects on legacy storage' do
      let(:project) { create(:project, :repository, :legacy_storage) }
1885

1886 1887
      it_behaves_like 'tracks storage location'
    end
1888

1889 1890
    context 'with projects on hashed storage' do
      let(:project) { create(:project, :repository) }
1891

1892
      it_behaves_like 'tracks storage location'
1893 1894 1895
    end
  end

1896
  describe '#create_repository' do
1897
    let(:project) { create(:project, :repository) }
1898 1899 1900

    context 'using a regular repository' do
      it 'creates the repository' do
1901
        expect(project.repository).to receive(:create_repository)
1902 1903 1904 1905
        expect(project.create_repository).to eq(true)
      end

      it 'adds an error if the repository could not be created' do
1906
        expect(project.repository).to receive(:create_repository) { raise 'Fail in test' }
1907 1908 1909 1910 1911 1912 1913 1914
        expect(project.create_repository).to eq(false)
        expect(project.errors).not_to be_empty
      end
    end

    context 'using a forked repository' do
      it 'does nothing' do
        expect(project).to receive(:forked?).and_return(true)
1915
        expect(project.repository).not_to receive(:create_repository)
1916 1917 1918 1919 1920

        project.create_repository
      end
    end
  end
1921

1922 1923 1924 1925
  describe '#ensure_repository' do
    let(:project) { create(:project, :repository) }

    it 'creates the repository if it not exist' do
1926
      allow(project).to receive(:repository_exists?).and_return(false)
1927
      expect(project).to receive(:create_repository).with(force: true)
1928 1929 1930 1931 1932

      project.ensure_repository
    end

    it 'does not create the repository if it exists' do
1933
      allow(project).to receive(:repository_exists?).and_return(true)
1934 1935 1936 1937 1938

      expect(project).not_to receive(:create_repository)

      project.ensure_repository
    end
1939 1940 1941

    it 'creates the repository if it is a fork' do
      expect(project).to receive(:forked?).and_return(true)
1942 1943
      expect(project).to receive(:repository_exists?).and_return(false)
      expect(project.repository).to receive(:create_repository) { true }
1944 1945 1946

      project.ensure_repository
    end
1947 1948
  end

1949 1950 1951 1952 1953 1954 1955 1956
  describe 'handling import URL' do
    it 'returns the sanitized URL' do
      project = create(:project, :import_started, import_url: 'http://user:pass@test.com')

      project.import_state.finish

      expect(project.reload.import_url).to eq('http://test.com')
    end
1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973

    it 'saves the url credentials percent decoded' do
      url = 'http://user:pass%21%3F%40@github.com/t.git'
      project = build(:project, import_url: url)

      # When the credentials are not decoded this expectation fails
      expect(project.import_url).to eq(url)
      expect(project.import_data.credentials).to eq(user: 'user', password: 'pass!?@')
    end

    it 'saves url with no credentials' do
      url = 'http://github.com/t.git'
      project = build(:project, import_url: url)

      expect(project.import_url).to eq(url)
      expect(project.import_data.credentials).to eq(user: nil, password: nil)
    end
1974 1975
  end

A
Andre Guedes 已提交
1976
  describe '#container_registry_url' do
1977
    let(:project) { create(:project) }
K
Kamil Trzcinski 已提交
1978

A
Andre Guedes 已提交
1979
    subject { project.container_registry_url }
K
Kamil Trzcinski 已提交
1980

1981 1982 1983
    before do
      stub_container_registry_config(**registry_settings)
    end
K
Kamil Trzcinski 已提交
1984 1985 1986

    context 'for enabled registry' do
      let(:registry_settings) do
1987 1988
        { enabled: true,
          host_port: 'example.com' }
K
Kamil Trzcinski 已提交
1989 1990
      end

1991
      it { is_expected.not_to be_nil }
K
Kamil Trzcinski 已提交
1992 1993 1994 1995
    end

    context 'for disabled registry' do
      let(:registry_settings) do
1996
        { enabled: false }
K
Kamil Trzcinski 已提交
1997 1998 1999 2000 2001 2002
      end

      it { is_expected.to be_nil }
    end
  end

2003
  describe '#has_container_registry_tags?' do
2004
    let(:project) { create(:project) }
2005 2006

    context 'when container registry is enabled' do
2007 2008 2009
      before do
        stub_container_registry_config(enabled: true)
      end
2010 2011 2012 2013 2014 2015 2016 2017 2018

      context 'when tags are present for multi-level registries' do
        before do
          create(:container_repository, project: project, name: 'image')

          stub_container_registry_tags(repository: /image/,
                                       tags: %w[latest rc1])
        end

2019
        it 'has image tags' do
2020 2021 2022 2023 2024 2025 2026 2027 2028 2029
          expect(project).to have_container_registry_tags
        end
      end

      context 'when tags are present for root repository' do
        before do
          stub_container_registry_tags(repository: project.full_path,
                                       tags: %w[latest rc1 pre1])
        end

2030
        it 'has image tags' do
2031 2032 2033 2034 2035 2036 2037 2038 2039
          expect(project).to have_container_registry_tags
        end
      end

      context 'when there are no tags at all' do
        before do
          stub_container_registry_tags(repository: :any, tags: [])
        end

2040
        it 'does not have image tags' do
2041 2042 2043 2044 2045 2046
          expect(project).not_to have_container_registry_tags
        end
      end
    end

    context 'when container registry is disabled' do
2047 2048 2049
      before do
        stub_container_registry_config(enabled: false)
      end
2050

2051
      it 'does not have image tags' do
2052 2053 2054
        expect(project).not_to have_container_registry_tags
      end

2055
      it 'does not check root repository tags' do
2056 2057 2058 2059
        expect(project).not_to receive(:full_path)
        expect(project).not_to have_container_registry_tags
      end

2060
      it 'iterates through container repositories' do
2061 2062 2063 2064 2065 2066
        expect(project).to receive(:container_repositories)
        expect(project).not_to have_container_registry_tags
      end
    end
  end

2067
  describe '#ci_config_path=' do
2068
    using RSpec::Parameterized::TableSyntax
2069

2070
    let(:project) { create(:project) }
2071

2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084
    where(:default_ci_config_path, :project_ci_config_path, :expected_ci_config_path) do
      nil           | :notset            | :default
      nil           | nil                | :default
      nil           | ''                 | :default
      nil           | "cust\0om/\0/path" | 'custom//path'
      ''            | :notset            | :default
      ''            | nil                | :default
      ''            | ''                 | :default
      ''            | "cust\0om/\0/path" | 'custom//path'
      'global/path' | :notset            | 'global/path'
      'global/path' | nil                | :default
      'global/path' | ''                 | :default
      'global/path' | "cust\0om/\0/path" | 'custom//path'
2085 2086
    end

2087 2088 2089
    with_them do
      before do
        stub_application_setting(default_ci_config_path: default_ci_config_path)
2090

2091 2092 2093 2094
        if project_ci_config_path != :notset
          project.ci_config_path = project_ci_config_path
        end
      end
2095

2096 2097 2098
      it 'returns the correct path' do
        expect(project.ci_config_path.presence || :default).to eq(expected_ci_config_path)
      end
2099 2100 2101
    end
  end

2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123
  describe '#uses_default_ci_config?' do
    let(:project) { build(:project)}

    it 'has a custom ci config path' do
      project.ci_config_path = 'something_custom'

      expect(project.uses_default_ci_config?).to be_falsey
    end

    it 'has a blank ci config path' do
      project.ci_config_path = ''

      expect(project.uses_default_ci_config?).to be_truthy
    end

    it 'does not have a custom ci config path' do
      project.ci_config_path = nil

      expect(project.uses_default_ci_config?).to be_truthy
    end
  end

2124
  describe '#latest_successful_build_for_ref' do
2125
    let(:project) { create(:project, :repository) }
2126
    let(:pipeline) { create_pipeline(project) }
L
Lin Jen-Shin 已提交
2127

2128
    it_behaves_like 'latest successful build for sha or ref'
L
Lin Jen-Shin 已提交
2129

2130
    subject { project.latest_successful_build_for_ref(build_name) }
2131

2132 2133
    context 'with a specified ref' do
      let(:build) { create_build }
2134

2135
      subject { project.latest_successful_build_for_ref(build.name, project.default_branch) }
2136

2137
      it { is_expected.to eq(build) }
2138 2139
    end
  end
2140

2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177
  describe '#latest_pipeline_for_ref' do
    let(:project) { create(:project, :repository) }
    let(:second_branch) { project.repository.branches[2] }

    let!(:pipeline_for_default_branch) do
      create(:ci_empty_pipeline, project: project, sha: project.commit.id,
                                 ref: project.default_branch)
    end
    let!(:pipeline_for_second_branch) do
      create(:ci_empty_pipeline, project: project, sha: second_branch.target,
                                 ref: second_branch.name)
    end

    before do
      create(:ci_empty_pipeline, project: project, sha: project.commit.parent.id,
                                 ref: project.default_branch)
    end

    context 'default repository branch' do
      subject { project.latest_pipeline_for_ref(project.default_branch) }

      it { is_expected.to eq(pipeline_for_default_branch) }
    end

    context 'provided ref' do
      subject { project.latest_pipeline_for_ref(second_branch.name) }

      it { is_expected.to eq(pipeline_for_second_branch) }
    end

    context 'bad ref' do
      subject { project.latest_pipeline_for_ref(SecureRandom.uuid) }

      it { is_expected.to be_nil }
    end
  end

2178 2179 2180
  describe '#latest_successful_build_for_sha' do
    let(:project) { create(:project, :repository) }
    let(:pipeline) { create_pipeline(project) }
2181

2182
    it_behaves_like 'latest successful build for sha or ref'
2183

2184
    subject { project.latest_successful_build_for_sha(build_name, project.commit.sha) }
2185 2186
  end

2187
  describe '#latest_successful_build_for_ref!' do
2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199
    let(:project) { create(:project, :repository) }
    let(:pipeline) { create_pipeline(project) }

    context 'with many builds' do
      it 'gives the latest builds from latest pipeline' do
        pipeline1 = create_pipeline(project)
        pipeline2 = create_pipeline(project)
        create_build(pipeline1, 'test')
        create_build(pipeline1, 'test2')
        build1_p2 = create_build(pipeline2, 'test')
        create_build(pipeline2, 'test2')

2200
        expect(project.latest_successful_build_for_ref!(build1_p2.name))
2201 2202 2203 2204 2205 2206 2207 2208 2209
          .to eq(build1_p2)
      end
    end

    context 'with succeeded pipeline' do
      let!(:build) { create_build }

      context 'standalone pipeline' do
        it 'returns builds for ref for default_branch' do
2210
          expect(project.latest_successful_build_for_ref!(build.name))
2211 2212 2213 2214
            .to eq(build)
        end

        it 'returns exception if the build cannot be found' do
2215
          expect { project.latest_successful_build_for_ref!(build.name, 'TAIL') }
2216
            .to raise_error(ActiveRecord::RecordNotFound)
2217
        end
2218 2219
      end

L
Lin Jen-Shin 已提交
2220
      context 'with some pending pipeline' do
2221
        before do
2222
          create_build(create_pipeline(project, 'pending'))
2223 2224
        end

L
Lin Jen-Shin 已提交
2225
        it 'gives the latest build from latest pipeline' do
2226
          expect(project.latest_successful_build_for_ref!(build.name))
2227
            .to eq(build)
2228
        end
2229 2230 2231 2232
      end
    end

    context 'with pending pipeline' do
2233
      it 'returns empty relation' do
2234
        pipeline.update(status: 'pending')
2235
        pending_build = create_build(pipeline)
2236

2237
        expect { project.latest_successful_build_for_ref!(pending_build.name) }
2238
          .to raise_error(ActiveRecord::RecordNotFound)
2239 2240 2241 2242
      end
    end
  end

2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257
  describe '#import_status' do
    context 'with import_state' do
      it 'returns the right status' do
        project = create(:project, :import_started)

        expect(project.import_status).to eq("started")
      end
    end

    context 'without import_state' do
      it 'returns none' do
        project = create(:project)

        expect(project.import_status).to eq('none')
      end
2258 2259 2260 2261
    end
  end

  describe '#jira_import_status' do
2262
    let(:project) { create(:project, import_type: 'jira') }
2263

2264
    context 'when no jira imports' do
2265
      it 'returns none' do
2266
        expect(project.jira_import_status).to eq('initial')
2267 2268 2269
      end
    end

2270 2271 2272
    context 'when there are jira imports' do
      let(:jira_import1) { build(:jira_import_state, :finished, project: project) }
      let(:jira_import2) { build(:jira_import_state, project: project) }
2273

2274 2275
      before do
        expect(project).to receive(:latest_jira_import).and_return(jira_import2)
2276 2277
      end

2278 2279 2280
      context 'when latest import status is initial or jira imports are mising' do
        it 'returns initial' do
          expect(project.jira_import_status).to eq('initial')
2281
        end
2282
      end
2283

2284
      context 'when latest import status is scheduled' do
2285
        before do
2286
          jira_import2.schedule!
2287 2288
        end

2289 2290
        it 'returns scheduled' do
          expect(project.jira_import_status).to eq('scheduled')
2291 2292
        end
      end
2293 2294 2295 2296 2297 2298 2299 2300
    end
  end

  describe '#human_import_status_name' do
    context 'with import_state' do
      it 'returns the right human import status' do
        project = create(:project, :import_started)

2301
        expect(project.human_import_status_name).to eq(_('started'))
2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313
      end
    end

    context 'without import_state' do
      it 'returns none' do
        project = create(:project)

        expect(project.human_import_status_name).to eq('none')
      end
    end
  end

2314
  describe '#add_import_job' do
2315 2316
    let(:import_jid) { '123' }

2317
    context 'forked' do
2318 2319 2320 2321 2322 2323
      let(:forked_from_project) { create(:project, :repository) }
      let(:project) { create(:project) }

      before do
        fork_project(forked_from_project, nil, target_project: project)
      end
2324 2325

      it 'schedules a RepositoryForkWorker job' do
2326
        expect(RepositoryForkWorker).to receive(:perform_async).with(project.id).and_return(import_jid)
2327

2328
        expect(project.add_import_job).to eq(import_jid)
2329
      end
2330 2331 2332 2333 2334 2335 2336 2337 2338

      context 'without repository' do
        it 'schedules RepositoryImportWorker' do
          project = create(:project, import_url: generate(:url))

          expect(RepositoryImportWorker).to receive(:perform_async).with(project.id).and_return(import_jid)
          expect(project.add_import_job).to eq(import_jid)
        end
      end
2339 2340 2341 2342
    end

    context 'not forked' do
      it 'schedules a RepositoryImportWorker job' do
2343
        project = create(:project, import_url: generate(:url))
2344

2345 2346
        expect(RepositoryImportWorker).to receive(:perform_async).with(project.id).and_return(import_jid)
        expect(project.add_import_job).to eq(import_jid)
2347 2348
      end
    end
2349 2350 2351 2352

    context 'jira import' do
      it 'schedules a jira import job' do
        project = create(:project, import_type: 'jira')
2353
        jira_import = create(:jira_import_state, project: project)
2354 2355

        expect(Gitlab::JiraImport::Stage::StartImportWorker).to receive(:perform_async).with(project.id).and_return(import_jid)
2356 2357 2358 2359

        jira_import.schedule!

        expect(jira_import.jid).to eq(import_jid)
2360 2361 2362 2363 2364
      end
    end
  end

  describe '#jira_import?' do
2365 2366
    let_it_be(:project) { build(:project, import_type: 'jira') }
    let_it_be(:jira_import) { build(:jira_import_state, project: project) }
2367

2368 2369
    before do
      expect(project).to receive(:jira_imports).and_return([jira_import])
2370 2371
    end

2372 2373
    it { expect(project.jira_import?).to be true }
    it { expect(project.import?).to be true }
2374 2375 2376
  end

  describe '#remove_import_data' do
2377
    let_it_be(:import_data) { ProjectImportData.new(data: { 'test' => 'some data' }) }
2378 2379

    context 'when jira import' do
2380 2381
      let_it_be(:project, reload: true) { create(:project, import_type: 'jira', import_data: import_data) }
      let_it_be(:jira_import) { create(:jira_import_state, project: project) }
2382

2383
      it 'does remove import data' do
2384 2385
        expect(project.mirror?).to be false
        expect(project.jira_import?).to be true
2386
        expect { project.remove_import_data }.to change { ProjectImportData.count }.by(-1)
2387 2388 2389
      end
    end

2390 2391 2392
    context 'when neither a mirror nor a jira import' do
      let_it_be(:user) { create(:user) }
      let_it_be(:project) { create(:project, import_type: 'github', import_data: import_data) }
2393 2394 2395 2396 2397 2398 2399

      it 'removes import data' do
        expect(project.mirror?).to be false
        expect(project.jira_import?).to be false
        expect { project.remove_import_data }.to change { ProjectImportData.count }.by(-1)
      end
    end
2400 2401
  end

R
Rémy Coutable 已提交
2402
  describe '#gitlab_project_import?' do
2403
    subject(:project) { build(:project, import_type: 'gitlab_project') }
R
Rémy Coutable 已提交
2404 2405 2406 2407 2408

    it { expect(project.gitlab_project_import?).to be true }
  end

  describe '#gitea_import?' do
2409
    subject(:project) { build(:project, import_type: 'gitea') }
R
Rémy Coutable 已提交
2410 2411 2412 2413

    it { expect(project.gitea_import?).to be true }
  end

2414
  describe '#has_remote_mirror?' do
2415
    let(:project) { create(:project, :remote_mirror, :import_started) }
2416

2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427
    subject { project.has_remote_mirror? }

    before do
      allow_any_instance_of(RemoteMirror).to receive(:refresh_remote)
    end

    it 'returns true when a remote mirror is enabled' do
      is_expected.to be_truthy
    end

    it 'returns false when remote mirror is disabled' do
L
Lin Jen-Shin 已提交
2428
      project.remote_mirrors.first.update(enabled: false)
2429 2430 2431 2432 2433 2434 2435

      is_expected.to be_falsy
    end
  end

  describe '#update_remote_mirrors' do
    let(:project) { create(:project, :remote_mirror, :import_started) }
2436

2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458
    delegate :update_remote_mirrors, to: :project

    before do
      allow_any_instance_of(RemoteMirror).to receive(:refresh_remote)
    end

    it 'syncs enabled remote mirror' do
      expect_any_instance_of(RemoteMirror).to receive(:sync)

      update_remote_mirrors
    end

    it 'does nothing when remote mirror is disabled globally and not overridden' do
      stub_application_setting(mirror_available: false)
      project.remote_mirror_available_overridden = false

      expect_any_instance_of(RemoteMirror).not_to receive(:sync)

      update_remote_mirrors
    end

    it 'does not sync disabled remote mirrors' do
L
Lin Jen-Shin 已提交
2459
      project.remote_mirrors.first.update(enabled: false)
2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492

      expect_any_instance_of(RemoteMirror).not_to receive(:sync)

      update_remote_mirrors
    end
  end

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

    context 'when remote mirror global setting is enabled' do
      it 'returns true' do
        expect(project.remote_mirror_available?).to be(true)
      end
    end

    context 'when remote mirror global setting is disabled' do
      before do
        stub_application_setting(mirror_available: false)
      end

      it 'returns true when overridden' do
        project.remote_mirror_available_overridden = true

        expect(project.remote_mirror_available?).to be(true)
      end

      it 'returns false when not overridden' do
        expect(project.remote_mirror_available?).to be(false)
      end
    end
  end

2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507
  describe '#mark_stuck_remote_mirrors_as_failed!' do
    it 'fails stuck remote mirrors' do
      project = create(:project, :repository, :remote_mirror)

      project.remote_mirrors.first.update(
        update_status: :started,
        last_update_started_at: 2.days.ago
      )

      expect do
        project.mark_stuck_remote_mirrors_as_failed!
      end.to change { project.remote_mirrors.stuck.count }.from(1).to(0)
    end
  end

2508
  describe '#ancestors_upto' do
2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520
    let(:parent) { create(:group) }
    let(:child) { create(:group, parent: parent) }
    let(:child2) { create(:group, parent: child) }
    let(:project) { create(:project, namespace: child2) }

    it 'returns all ancestors when no namespace is given' do
      expect(project.ancestors_upto).to contain_exactly(child2, child, parent)
    end

    it 'includes ancestors upto but excluding the given ancestor' do
      expect(project.ancestors_upto(parent)).to contain_exactly(child2, child)
    end
2521 2522 2523 2524 2525 2526 2527 2528 2529 2530

    describe 'with hierarchy_order' do
      it 'returns ancestors ordered by descending hierarchy' do
        expect(project.ancestors_upto(hierarchy_order: :desc)).to eq([parent, child, child2])
      end

      it 'can be used with upto option' do
        expect(project.ancestors_upto(parent, hierarchy_order: :desc)).to eq([child, child2])
      end
    end
2531 2532
  end

2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546
  describe '#root_ancestor' do
    let(:project) { create(:project) }

    subject { project.root_ancestor }

    it { is_expected.to eq(project.namespace) }

    context 'in a group' do
      let(:group) { create(:group) }
      let(:project) { create(:project, group: group) }

      it { is_expected.to eq(group) }
    end

2547
    context 'in a nested group' do
2548 2549 2550 2551 2552 2553
      let(:root) { create(:group) }
      let(:child) { create(:group, parent: root) }
      let(:project) { create(:project, group: child) }

      it { is_expected.to eq(root) }
    end
2554 2555
  end

2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583
  describe '#emails_disabled?' do
    let(:project) { create(:project, emails_disabled: false) }

    context 'emails disabled in group' do
      it 'returns true' do
        allow(project.namespace).to receive(:emails_disabled?) { true }

        expect(project.emails_disabled?).to be_truthy
      end
    end

    context 'emails enabled in group' do
      before do
        allow(project.namespace).to receive(:emails_disabled?) { false }
      end

      it 'returns false' do
        expect(project.emails_disabled?).to be_falsey
      end

      it 'returns true' do
        project.update_attribute(:emails_disabled, true)

        expect(project.emails_disabled?).to be_truthy
      end
    end
  end

2584
  describe '#lfs_enabled?' do
2585
    let(:project) { create(:project) }
2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645

    shared_examples 'project overrides group' do
      it 'returns true when enabled in project' do
        project.update_attribute(:lfs_enabled, true)

        expect(project.lfs_enabled?).to be_truthy
      end

      it 'returns false when disabled in project' do
        project.update_attribute(:lfs_enabled, false)

        expect(project.lfs_enabled?).to be_falsey
      end

      it 'returns the value from the namespace, when no value is set in project' do
        expect(project.lfs_enabled?).to eq(project.namespace.lfs_enabled?)
      end
    end

    context 'LFS disabled in group' do
      before do
        project.namespace.update_attribute(:lfs_enabled, false)
        enable_lfs
      end

      it_behaves_like 'project overrides group'
    end

    context 'LFS enabled in group' do
      before do
        project.namespace.update_attribute(:lfs_enabled, true)
        enable_lfs
      end

      it_behaves_like 'project overrides group'
    end

    describe 'LFS disabled globally' do
      shared_examples 'it always returns false' do
        it do
          expect(project.lfs_enabled?).to be_falsey
          expect(project.namespace.lfs_enabled?).to be_falsey
        end
      end

      context 'when no values are set' do
        it_behaves_like 'it always returns false'
      end

      context 'when all values are set to true' do
        before do
          project.namespace.update_attribute(:lfs_enabled, true)
          project.update_attribute(:lfs_enabled, true)
        end

        it_behaves_like 'it always returns false'
      end
    end
  end

2646
  describe '#change_head' do
2647
    let(:project) { create(:project, :repository) }
2648

2649 2650 2651 2652 2653
    it 'returns error if branch does not exist' do
      expect(project.change_head('unexisted-branch')).to be false
      expect(project.errors.size).to eq(1)
    end

2654
    it 'calls the before_change_head and after_change_head methods' do
2655
      expect(project.repository).to receive(:before_change_head)
2656 2657
      expect(project.repository).to receive(:after_change_head)

2658 2659 2660
      project.change_head(project.default_branch)
    end

2661 2662 2663 2664 2665 2666
    it 'updates commit count' do
      expect(ProjectCacheWorker).to receive(:perform_async).with(project.id, [], [:commit_count])

      project.change_head(project.default_branch)
    end

2667 2668 2669 2670 2671 2672 2673 2674 2675 2676
    it 'copies the gitattributes' do
      expect(project.repository).to receive(:copy_gitattributes).with(project.default_branch)
      project.change_head(project.default_branch)
    end

    it 'reloads the default branch' do
      expect(project).to receive(:reload_default_branch)
      project.change_head(project.default_branch)
    end
  end
Y
Yorick Peterse 已提交
2677

2678
  context 'forks' do
B
Bob Van Landuyt 已提交
2679 2680
    include ProjectForksHelper

2681 2682 2683 2684 2685
    let(:project) { create(:project, :public) }
    let!(:forked_project) { fork_project(project) }

    describe '#fork_network' do
      it 'includes a fork of the project' do
B
Bob Van Landuyt 已提交
2686
        expect(project.fork_network.projects).to include(forked_project)
2687 2688 2689 2690 2691
      end

      it 'includes a fork of a fork' do
        other_fork = fork_project(forked_project)

B
Bob Van Landuyt 已提交
2692
        expect(project.fork_network.projects).to include(other_fork)
2693 2694 2695 2696 2697
      end

      it 'includes sibling forks' do
        other_fork = fork_project(project)

B
Bob Van Landuyt 已提交
2698
        expect(forked_project.fork_network.projects).to include(other_fork)
2699 2700 2701
      end

      it 'includes the base project' do
B
Bob Van Landuyt 已提交
2702
        expect(forked_project.fork_network.projects).to include(project.reload)
2703 2704 2705 2706 2707 2708 2709 2710
      end
    end

    describe '#in_fork_network_of?' do
      it 'is true for a real fork' do
        expect(forked_project.in_fork_network_of?(project)).to be_truthy
      end

2711
      it 'is true for a fork of a fork' do
2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728
        other_fork = fork_project(forked_project)

        expect(other_fork.in_fork_network_of?(project)).to be_truthy
      end

      it 'is true for sibling forks' do
        sibling = fork_project(project)

        expect(sibling.in_fork_network_of?(forked_project)).to be_truthy
      end

      it 'is false when another project is given' do
        other_project = build_stubbed(:project)

        expect(forked_project.in_fork_network_of?(other_project)).to be_falsy
      end
    end
2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741

    describe '#fork_source' do
      let!(:second_fork) { fork_project(forked_project) }

      it 'returns the direct source if it exists' do
        expect(second_fork.fork_source).to eq(forked_project)
      end

      it 'returns the root of the fork network when the directs source was deleted' do
        forked_project.destroy

        expect(second_fork.fork_source).to eq(project)
      end
2742 2743 2744 2745

      it 'returns nil if it is the root of the fork network' do
        expect(project.fork_source).to be_nil
      end
2746
    end
2747

2748 2749 2750 2751 2752 2753
    describe '#forks' do
      it 'includes direct forks of the project' do
        expect(project.forks).to contain_exactly(forked_project)
      end
    end

2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770
    describe '#lfs_storage_project' do
      it 'returns self for non-forks' do
        expect(project.lfs_storage_project).to eq project
      end

      it 'returns the fork network root for forks' do
        second_fork = fork_project(forked_project)

        expect(second_fork.lfs_storage_project).to eq project
      end

      it 'returns self when fork_source is nil' do
        expect(forked_project).to receive(:fork_source).and_return(nil)

        expect(forked_project.lfs_storage_project).to eq forked_project
      end
    end
2771 2772 2773 2774

    describe '#all_lfs_objects' do
      let(:lfs_object) { create(:lfs_object) }

2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786
      context 'when LFS object is only associated to the source' do
        before do
          project.lfs_objects << lfs_object
        end

        it 'returns the lfs object for a project' do
          expect(project.all_lfs_objects).to contain_exactly(lfs_object)
        end

        it 'returns the lfs object for a fork' do
          expect(forked_project.all_lfs_objects).to contain_exactly(lfs_object)
        end
2787 2788
      end

2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800
      context 'when LFS object is only associated to the fork' do
        before do
          forked_project.lfs_objects << lfs_object
        end

        it 'returns nothing' do
          expect(project.all_lfs_objects).to be_empty
        end

        it 'returns the lfs object for a fork' do
          expect(forked_project.all_lfs_objects).to contain_exactly(lfs_object)
        end
2801 2802
      end

2803 2804 2805 2806 2807 2808 2809 2810 2811 2812
      context 'when LFS object is associated to both source and fork' do
        before do
          project.lfs_objects << lfs_object
          forked_project.lfs_objects << lfs_object
        end

        it 'returns the lfs object for the source and fork' do
          expect(project.all_lfs_objects).to contain_exactly(lfs_object)
          expect(forked_project.all_lfs_objects).to contain_exactly(lfs_object)
        end
2813 2814
      end
    end
2815 2816
  end

2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830
  describe '#set_repository_read_only!' do
    let(:project) { create(:project) }

    it 'returns true when there is no existing git transfer in progress' do
      expect(project.set_repository_read_only!).to be_truthy
    end

    it 'returns false when there is an existing git transfer in progress' do
      allow(project).to receive(:git_transfer_in_progress?) { true }

      expect(project.set_repository_read_only!).to be_falsey
    end
  end

J
John Cai 已提交
2831 2832 2833 2834 2835 2836 2837 2838 2839 2840
  describe '#set_repository_writable!' do
    it 'sets repository_read_only to false' do
      project = create(:project, :read_only)

      expect { project.set_repository_writable! }
        .to change(project, :repository_read_only)
        .from(true).to(false)
    end
  end

Y
Yorick Peterse 已提交
2841
  describe '#pushes_since_gc' do
2842
    let(:project) { create(:project) }
Y
Yorick Peterse 已提交
2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863

    after do
      project.reset_pushes_since_gc
    end

    context 'without any pushes' do
      it 'returns 0' do
        expect(project.pushes_since_gc).to eq(0)
      end
    end

    context 'with a number of pushes' do
      it 'returns the number of pushes' do
        3.times { project.increment_pushes_since_gc }

        expect(project.pushes_since_gc).to eq(3)
      end
    end
  end

  describe '#increment_pushes_since_gc' do
2864
    let(:project) { create(:project) }
Y
Yorick Peterse 已提交
2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877

    after do
      project.reset_pushes_since_gc
    end

    it 'increments the number of pushes since the last GC' do
      3.times { project.increment_pushes_since_gc }

      expect(project.pushes_since_gc).to eq(3)
    end
  end

  describe '#reset_pushes_since_gc' do
2878
    let(:project) { create(:project) }
Y
Yorick Peterse 已提交
2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891

    after do
      project.reset_pushes_since_gc
    end

    it 'resets the number of pushes since the last GC' do
      3.times { project.increment_pushes_since_gc }

      project.reset_pushes_since_gc

      expect(project.pushes_since_gc).to eq(0)
    end
  end
2892

2893
  describe '#deployment_variables' do
2894 2895
    let(:project) { create(:project) }
    let(:environment) { 'production' }
2896
    let(:namespace) { 'namespace' }
2897

2898
    subject { project.deployment_variables(environment: environment, kubernetes_namespace: namespace) }
2899 2900 2901 2902

    before do
      expect(project).to receive(:deployment_platform).with(environment: environment)
        .and_return(deployment_platform)
2903 2904
    end

2905 2906
    context 'when project has no deployment platform' do
      let(:deployment_platform) { nil }
2907

2908
      it { is_expected.to eq [] }
2909 2910
    end

2911 2912 2913
    context 'when project has a deployment platform' do
      let(:platform_variables) { %w(platform variables) }
      let(:deployment_platform) { double }
J
James Fargher 已提交
2914

2915 2916
      before do
        expect(deployment_platform).to receive(:predefined_variables)
2917
          .with(project: project, environment_name: environment, kubernetes_namespace: namespace)
2918
          .and_return(platform_variables)
2919 2920
      end

2921
      it { is_expected.to eq platform_variables }
2922 2923 2924
    end
  end

2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946
  describe '#default_environment' do
    let(:project) { create(:project) }

    it 'returns production environment when it exists' do
      production = create(:environment, name: "production", project: project)
      create(:environment, name: 'staging', project: project)

      expect(project.default_environment).to eq(production)
    end

    it 'returns first environment when no production environment exists' do
      create(:environment, name: 'staging', project: project)
      create(:environment, name: 'foo', project: project)

      expect(project.default_environment).to eq(project.environments.first)
    end

    it 'returns nil when no available environment exists' do
      expect(project.default_environment).to be_nil
    end
  end

2947
  describe '#ci_variables_for' do
2948
    let(:project) { create(:project) }
2949
    let(:environment_scope) { '*' }
2950

2951
    let!(:ci_variable) do
2952
      create(:ci_variable, value: 'secret', project: project, environment_scope: environment_scope)
2953 2954 2955 2956 2957 2958
    end

    let!(:protected_variable) do
      create(:ci_variable, :protected, value: 'protected', project: project)
    end

2959
    subject { project.reload.ci_variables_for(ref: 'ref') }
L
Lin Jen-Shin 已提交
2960 2961 2962 2963 2964

    before do
      stub_application_setting(
        default_branch_protection: Gitlab::Access::PROTECTION_NONE)
    end
2965 2966 2967

    shared_examples 'ref is protected' do
      it 'contains all the variables' do
2968
        is_expected.to contain_exactly(ci_variable, protected_variable)
2969 2970 2971
      end
    end

2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984
    it 'memoizes the result by ref and environment', :request_store do
      scoped_variable = create(:ci_variable, value: 'secret', project: project, environment_scope: 'scoped')

      expect(project).to receive(:protected_for?).with('ref').once.and_return(true)
      expect(project).to receive(:protected_for?).with('other').twice.and_return(false)

      2.times do
        expect(project.reload.ci_variables_for(ref: 'ref', environment: 'production')).to contain_exactly(ci_variable, protected_variable)
        expect(project.reload.ci_variables_for(ref: 'other')).to contain_exactly(ci_variable)
        expect(project.reload.ci_variables_for(ref: 'other', environment: 'scoped')).to contain_exactly(ci_variable, scoped_variable)
      end
    end

2985
    context 'when the ref is not protected' do
2986 2987 2988 2989
      before do
        allow(project).to receive(:protected_for?).with('ref').and_return(false)
      end

2990 2991
      it 'contains only the CI variables' do
        is_expected.to contain_exactly(ci_variable)
2992 2993 2994
      end
    end

2995 2996
    context 'when the ref is a protected branch' do
      before do
2997
        allow(project).to receive(:protected_for?).with('ref').and_return(true)
2998
      end
2999 3000 3001 3002 3003 3004

      it_behaves_like 'ref is protected'
    end

    context 'when the ref is a protected tag' do
      before do
3005
        allow(project).to receive(:protected_for?).with('ref').and_return(true)
3006 3007 3008
      end

      it_behaves_like 'ref is protected'
3009
    end
3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099

    context 'when environment name is specified' do
      let(:environment) { 'review/name' }

      subject do
        project.ci_variables_for(ref: 'ref', environment: environment)
      end

      context 'when environment scope is exactly matched' do
        let(:environment_scope) { 'review/name' }

        it { is_expected.to contain_exactly(ci_variable) }
      end

      context 'when environment scope is matched by wildcard' do
        let(:environment_scope) { 'review/*' }

        it { is_expected.to contain_exactly(ci_variable) }
      end

      context 'when environment scope does not match' do
        let(:environment_scope) { 'review/*/special' }

        it { is_expected.not_to contain_exactly(ci_variable) }
      end

      context 'when environment scope has _' do
        let(:environment_scope) { '*_*' }

        it 'does not treat it as wildcard' do
          is_expected.not_to contain_exactly(ci_variable)
        end

        context 'when environment name contains underscore' do
          let(:environment) { 'foo_bar/test' }
          let(:environment_scope) { 'foo_bar/*' }

          it 'matches literally for _' do
            is_expected.to contain_exactly(ci_variable)
          end
        end
      end

      # The environment name and scope cannot have % at the moment,
      # but we're considering relaxing it and we should also make sure
      # it doesn't break in case some data sneaked in somehow as we're
      # not checking this integrity in database level.
      context 'when environment scope has %' do
        it 'does not treat it as wildcard' do
          ci_variable.update_attribute(:environment_scope, '*%*')

          is_expected.not_to contain_exactly(ci_variable)
        end

        context 'when environment name contains a percent' do
          let(:environment) { 'foo%bar/test' }

          it 'matches literally for _' do
            ci_variable.update(environment_scope: 'foo%bar/*')

            is_expected.to contain_exactly(ci_variable)
          end
        end
      end

      context 'when variables with the same name have different environment scopes' do
        let!(:partially_matched_variable) do
          create(:ci_variable,
                 key: ci_variable.key,
                 value: 'partial',
                 environment_scope: 'review/*',
                 project: project)
        end

        let!(:perfectly_matched_variable) do
          create(:ci_variable,
                 key: ci_variable.key,
                 value: 'prefect',
                 environment_scope: 'review/name',
                 project: project)
        end

        it 'puts variables matching environment scope more in the end' do
          is_expected.to eq(
            [ci_variable,
             partially_matched_variable,
             perfectly_matched_variable])
        end
      end
    end
3100 3101
  end

3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140
  describe '#ci_instance_variables_for' do
    let(:project) { create(:project) }

    let!(:instance_variable) do
      create(:ci_instance_variable, value: 'secret')
    end

    let!(:protected_instance_variable) do
      create(:ci_instance_variable, :protected, value: 'protected')
    end

    subject { project.ci_instance_variables_for(ref: 'ref') }

    before do
      stub_application_setting(
        default_branch_protection: Gitlab::Access::PROTECTION_NONE)
    end

    context 'when the ref is not protected' do
      before do
        allow(project).to receive(:protected_for?).with('ref').and_return(false)
      end

      it 'contains only the CI variables' do
        is_expected.to contain_exactly(instance_variable)
      end
    end

    context 'when the ref is protected' do
      before do
        allow(project).to receive(:protected_for?).with('ref').and_return(true)
      end

      it 'contains all the variables' do
        is_expected.to contain_exactly(instance_variable, protected_instance_variable)
      end
    end
  end

3141
  describe '#any_lfs_file_locks?', :request_store do
3142
    let_it_be(:project) { create(:project) }
3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156

    it 'returns false when there are no LFS file locks' do
      expect(project.any_lfs_file_locks?).to be_falsey
    end

    it 'returns a cached true when there are LFS file locks' do
      create(:lfs_file_lock, project: project)

      expect(project.lfs_file_locks).to receive(:any?).once.and_call_original

      2.times { expect(project.any_lfs_file_locks?).to be_truthy }
    end
  end

3157
  describe '#protected_for?' do
3158
    let(:project) { create(:project, :repository) }
3159

3160
    subject { project.protected_for?(ref) }
3161

3162
    shared_examples 'ref is not protected' do
3163 3164 3165 3166 3167 3168
      before do
        stub_application_setting(
          default_branch_protection: Gitlab::Access::PROTECTION_NONE)
      end

      it 'returns false' do
3169
        is_expected.to be false
3170 3171 3172
      end
    end

3173
    shared_examples 'ref is protected branch' do
3174
      before do
3175
        create(:protected_branch, name: 'master', project: project)
3176 3177 3178
      end

      it 'returns true' do
3179
        is_expected.to be true
3180 3181 3182
      end
    end

3183
    shared_examples 'ref is protected tag' do
3184
      before do
3185
        create(:protected_tag, name: 'v1.0.0', project: project)
3186 3187 3188
      end

      it 'returns true' do
3189
        is_expected.to be true
3190 3191
      end
    end
3192

3193 3194
    context 'when ref is nil' do
      let(:ref) { nil }
3195

3196
      it 'returns false' do
3197
        is_expected.to be false
3198
      end
3199 3200 3201 3202 3203
    end

    context 'when ref is ref name' do
      context 'when ref is ambiguous' do
        let(:ref) { 'ref' }
3204 3205

        before do
3206 3207
          project.repository.add_branch(project.creator, 'ref', 'master')
          project.repository.add_tag(project.creator, 'ref', 'master')
3208 3209
        end

3210 3211
        it 'raises an error' do
          expect { subject }.to raise_error(Repository::AmbiguousRefError)
3212 3213
        end
      end
3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231

      context 'when the ref is not protected' do
        let(:ref) { 'master' }

        it_behaves_like 'ref is not protected'
      end

      context 'when the ref is a protected branch' do
        let(:ref) { 'master' }

        it_behaves_like 'ref is protected branch'
      end

      context 'when the ref is a protected tag' do
        let(:ref) { 'v1.0.0' }

        it_behaves_like 'ref is protected tag'
      end
3232 3233 3234 3235 3236 3237 3238 3239

      context 'when ref does not exist' do
        let(:ref) { 'something' }

        it 'returns false' do
          is_expected.to be false
        end
      end
3240 3241
    end

3242 3243 3244
    context 'when ref is full ref' do
      context 'when the ref is not protected' do
        let(:ref) { 'refs/heads/master' }
3245

3246
        it_behaves_like 'ref is not protected'
3247 3248
      end

3249 3250 3251 3252 3253 3254 3255 3256 3257 3258
      context 'when the ref is a protected branch' do
        let(:ref) { 'refs/heads/master' }

        it_behaves_like 'ref is protected branch'
      end

      context 'when the ref is a protected tag' do
        let(:ref) { 'refs/tags/v1.0.0' }

        it_behaves_like 'ref is protected tag'
3259 3260
      end

3261 3262 3263
      context 'when branch ref name is a full tag ref' do
        let(:ref) { 'refs/tags/something' }

3264
        before do
3265
          project.repository.add_branch(project.creator, ref, 'master')
3266 3267
        end

3268 3269
        context 'when ref is not protected' do
          it 'returns false' do
3270
            is_expected.to be false
3271 3272 3273 3274 3275 3276 3277 3278 3279
          end
        end

        context 'when ref is a protected branch' do
          before do
            create(:protected_branch, name: 'refs/tags/something', project: project)
          end

          it 'returns true' do
3280
            is_expected.to be true
3281
          end
3282 3283
        end
      end
3284 3285 3286 3287 3288 3289 3290

      context 'when ref does not exist' do
        let(:ref) { 'refs/heads/something' }

        it 'returns false' do
          is_expected.to be false
        end
3291 3292 3293 3294
      end
    end
  end

M
Markus Koller 已提交
3295
  describe '#update_project_statistics' do
3296
    let(:project) { create(:project) }
M
Markus Koller 已提交
3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314

    it "is called after creation" do
      expect(project.statistics).to be_a ProjectStatistics
      expect(project.statistics).to be_persisted
    end

    it "copies the namespace_id" do
      expect(project.statistics.namespace_id).to eq project.namespace_id
    end

    it "updates the namespace_id when changed" do
      namespace = create(:namespace)
      project.update(namespace: namespace)

      expect(project.statistics.namespace_id).to eq namespace.id
    end
  end

3315
  describe 'inside_path' do
3316 3317 3318
    let!(:project1) { create(:project, namespace: create(:namespace, path: 'name_pace')) }
    let!(:project2) { create(:project) }
    let!(:project3) { create(:project, namespace: create(:namespace, path: 'namespace')) }
3319
    let!(:path) { project1.namespace.full_path }
3320

3321
    it 'returns correct project' do
3322
      expect(described_class.inside_path(path)).to eq([project1])
3323
    end
3324 3325
  end

D
Douwe Maan 已提交
3326
  describe '#route_map_for' do
3327
    let(:project) { create(:project, :repository) }
D
Douwe Maan 已提交
3328 3329 3330 3331 3332 3333 3334 3335
    let(:route_map) do
      <<-MAP.strip_heredoc
      - source: /source/(.*)/
        public: '\\1'
      MAP
    end

    before do
3336
      project.repository.create_file(User.last, '.gitlab/route-map.yml', route_map, message: 'Add .gitlab/route-map.yml', branch_name: 'master')
D
Douwe Maan 已提交
3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363
    end

    context 'when there is a .gitlab/route-map.yml at the commit' do
      context 'when the route map is valid' do
        it 'returns a route map' do
          map = project.route_map_for(project.commit.sha)
          expect(map).to be_a_kind_of(Gitlab::RouteMap)
        end
      end

      context 'when the route map is invalid' do
        let(:route_map) { 'INVALID' }

        it 'returns nil' do
          expect(project.route_map_for(project.commit.sha)).to be_nil
        end
      end
    end

    context 'when there is no .gitlab/route-map.yml at the commit' do
      it 'returns nil' do
        expect(project.route_map_for(project.commit.parent.sha)).to be_nil
      end
    end
  end

  describe '#public_path_for_source_path' do
3364
    let(:project) { create(:project, :repository) }
D
Douwe Maan 已提交
3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388
    let(:route_map) do
      Gitlab::RouteMap.new(<<-MAP.strip_heredoc)
        - source: /source/(.*)/
          public: '\\1'
      MAP
    end
    let(:sha) { project.commit.id }

    context 'when there is a route map' do
      before do
        allow(project).to receive(:route_map_for).with(sha).and_return(route_map)
      end

      context 'when the source path is mapped' do
        it 'returns the public path' do
          expect(project.public_path_for_source_path('source/file.html', sha)).to eq('file.html')
        end
      end

      context 'when the source path is not mapped' do
        it 'returns nil' do
          expect(project.public_path_for_source_path('file.html', sha)).to be_nil
        end
      end
3389 3390 3391 3392 3393 3394 3395 3396 3397 3398

      it 'returns a public path with a leading slash unmodified' do
        route_map = Gitlab::RouteMap.new(<<-MAP.strip_heredoc)
          - source: 'source/file.html'
            public: '/public/file'
        MAP
        allow(project).to receive(:route_map_for).with(sha).and_return(route_map)

        expect(project.public_path_for_source_path('source/file.html', sha)).to eq('/public/file')
      end
D
Douwe Maan 已提交
3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411
    end

    context 'when there is no route map' do
      before do
        allow(project).to receive(:route_map_for).with(sha).and_return(nil)
      end

      it 'returns nil' do
        expect(project.public_path_for_source_path('source/file.html', sha)).to be_nil
      end
    end
  end

3412
  describe '#parent' do
3413
    let(:project) { create(:project) }
3414 3415 3416 3417

    it { expect(project.parent).to eq(project.namespace) }
  end

3418 3419 3420 3421 3422 3423
  describe '#parent_id' do
    let(:project) { create(:project) }

    it { expect(project.parent_id).to eq(project.namespace_id) }
  end

3424
  describe '#parent_changed?' do
3425
    let(:project) { create(:project) }
3426

3427 3428 3429
    before do
      project.namespace_id = 7
    end
3430 3431 3432 3433

    it { expect(project.parent_changed?).to be_truthy }
  end

3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481
  describe '#default_merge_request_target' do
    context 'when forked from a more visible project' do
      it 'returns the more restrictive project' do
        project = create(:project, :public)
        forked = fork_project(project)
        forked.visibility = Gitlab::VisibilityLevel::PRIVATE
        forked.save!

        expect(project.visibility).to eq 'public'
        expect(forked.visibility).to eq 'private'

        expect(forked.default_merge_request_target).to eq(forked)
      end
    end

    context 'when forked from a project with disabled merge requests' do
      it 'returns the current project' do
        project = create(:project, :merge_requests_disabled)
        forked = fork_project(project)

        expect(forked.forked_from_project).to receive(:merge_requests_enabled?)
          .and_call_original

        expect(forked.default_merge_request_target).to eq(forked)
      end
    end

    context 'when forked from a project with enabled merge requests' do
      it 'returns the source project' do
        project = create(:project, :public)
        forked = fork_project(project)

        expect(project.visibility).to eq 'public'
        expect(forked.visibility).to eq 'public'

        expect(forked.default_merge_request_target).to eq(project)
      end
    end

    context 'when not forked' do
      it 'returns the current project' do
        project = build_stubbed(:project)

        expect(project.default_merge_request_target).to eq(project)
      end
    end
  end

3482 3483 3484
  def enable_lfs
    allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
  end
K
Kamil Trzcinski 已提交
3485

3486
  describe '#pages_url' do
3487 3488
    let(:group) { create(:group, name: 'Group') }
    let(:nested_group) { create(:group, parent: group) }
K
Kamil Trzcinski 已提交
3489 3490 3491 3492 3493 3494 3495 3496 3497
    let(:domain) { 'Example.com' }

    subject { project.pages_url }

    before do
      allow(Settings.pages).to receive(:host).and_return(domain)
      allow(Gitlab.config.pages).to receive(:url).and_return('http://example.com')
    end

3498
    context 'top-level group' do
3499
      let(:project) { create(:project, namespace: group, name: project_name) }
K
Kamil Trzcinski 已提交
3500

3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511
      context 'group page' do
        let(:project_name) { 'group.example.com' }

        it { is_expected.to eq("http://group.example.com") }
      end

      context 'project page' do
        let(:project_name) { 'Project' }

        it { is_expected.to eq("http://group.example.com/project") }
      end
K
Kamil Trzcinski 已提交
3512 3513
    end

3514
    context 'nested group' do
3515
      let(:project) { create(:project, namespace: nested_group, name: project_name) }
3516
      let(:expected_url) { "http://group.example.com/#{nested_group.path}/#{project.path}" }
K
Kamil Trzcinski 已提交
3517

3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528
      context 'group page' do
        let(:project_name) { 'group.example.com' }

        it { is_expected.to eq(expected_url) }
      end

      context 'project page' do
        let(:project_name) { 'Project' }

        it { is_expected.to eq(expected_url) }
      end
K
Kamil Trzcinski 已提交
3529 3530
    end
  end
3531

3532 3533 3534
  describe '#lfs_http_url_to_repo' do
    let(:project) { create(:project) }

3535 3536 3537 3538 3539 3540 3541 3542
    context 'when a custom HTTP clone URL root is not set' do
      it 'returns the url to the repo without a username' do
        lfs_http_url_to_repo = project.lfs_http_url_to_repo('operation_that_doesnt_matter')

        expect(lfs_http_url_to_repo).to eq("#{project.web_url}.git")
        expect(lfs_http_url_to_repo).not_to include('@')
      end
    end
3543

3544 3545 3546 3547 3548 3549 3550 3551 3552 3553
    context 'when a custom HTTP clone URL root is set' do
      before do
        stub_application_setting(custom_http_clone_url_root: 'https://git.example.com:51234')
      end

      it 'returns the url to the repo, with the root replaced with the custom one' do
        lfs_http_url_to_repo = project.lfs_http_url_to_repo('operation_that_doesnt_matter')

        expect(lfs_http_url_to_repo).to eq("https://git.example.com:51234/#{project.full_path}.git")
      end
3554 3555 3556
    end
  end

3557
  describe '#pipeline_status' do
3558
    let(:project) { create(:project, :repository) }
3559

3560
    it 'builds a pipeline status' do
3561
      expect(project.pipeline_status).to be_a(Gitlab::Cache::Ci::ProjectPipelineStatus)
3562 3563 3564 3565 3566 3567
    end

    it 'hase a loaded pipeline status' do
      expect(project.pipeline_status).to be_loaded
    end
  end
3568

3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582
  describe '#update' do
    let(:project) { create(:project) }

    it 'validates the visibility' do
      expect(project).to receive(:visibility_level_allowed_as_fork).and_call_original
      expect(project).to receive(:visibility_level_allowed_by_group).and_call_original

      project.update(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
    end

    it 'does not validate the visibility' do
      expect(project).not_to receive(:visibility_level_allowed_as_fork).and_call_original
      expect(project).not_to receive(:visibility_level_allowed_by_group).and_call_original

3583
      project.update(updated_at: Time.current)
3584 3585 3586
    end
  end

3587 3588
  describe '#last_repository_updated_at' do
    it 'sets to created_at upon creation' do
3589
      project = create(:project, created_at: 2.hours.ago)
3590 3591 3592 3593

      expect(project.last_repository_updated_at.to_i).to eq(project.created_at.to_i)
    end
  end
3594 3595 3596 3597 3598

  describe '.public_or_visible_to_user' do
    let!(:user) { create(:user) }

    let!(:private_project) do
3599
      create(:project, :private, creator: user, namespace: user.namespace)
3600 3601
    end

3602
    let!(:public_project) { create(:project, :public) }
3603 3604 3605

    context 'with a user' do
      let(:projects) do
3606
        described_class.all.public_or_visible_to_user(user)
3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619
      end

      it 'includes projects the user has access to' do
        expect(projects).to include(private_project)
      end

      it 'includes projects the user can see' do
        expect(projects).to include(public_project)
      end
    end

    context 'without a user' do
      it 'only includes public projects' do
3620
        projects = described_class.all.public_or_visible_to_user
3621 3622 3623 3624

        expect(projects).to eq([public_project])
      end
    end
3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638

    context 'min_access_level' do
      let!(:private_project) { create(:project, :private) }

      before do
        private_project.add_guest(user)
      end

      it 'excludes projects when user does not have required minimum access level' do
        projects = described_class.all.public_or_visible_to_user(user, Gitlab::Access::REPORTER)

        expect(projects).to contain_exactly(public_project)
      end
    end
3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656

    context 'with deploy token users' do
      let_it_be(:private_project) { create(:project, :private) }

      subject { described_class.all.public_or_visible_to_user(user) }

      context 'deploy token user without project' do
        let_it_be(:user) { create(:deploy_token) }

        it { is_expected.to eq [] }
      end

      context 'deploy token user with project' do
        let_it_be(:user) { create(:deploy_token, projects: [private_project]) }

        it { is_expected.to include(private_project) }
      end
    end
3657
  end
3658

3659
  describe '.ids_with_issuables_available_for' do
3660 3661 3662 3663 3664 3665 3666 3667 3668
    let!(:user) { create(:user) }

    it 'returns project ids with milestones available for user' do
      project_1 = create(:project, :public, :merge_requests_disabled, :issues_disabled)
      project_2 = create(:project, :public, :merge_requests_disabled)
      project_3 = create(:project, :public, :issues_disabled)
      project_4 = create(:project, :public)
      project_4.project_feature.update(issues_access_level: ProjectFeature::PRIVATE, merge_requests_access_level: ProjectFeature::PRIVATE )

3669
      project_ids = described_class.ids_with_issuables_available_for(user).pluck(:id)
3670 3671 3672 3673 3674 3675

      expect(project_ids).to include(project_2.id, project_3.id)
      expect(project_ids).not_to include(project_1.id, project_4.id)
    end
  end

3676
  describe '.with_feature_available_for_user' do
3677 3678
    let(:user) { create(:user) }
    let(:feature) { MergeRequest }
3679 3680 3681

    subject { described_class.with_feature_available_for_user(feature, user) }

3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714
    shared_examples 'feature disabled' do
      let(:project) { create(:project, :public, :merge_requests_disabled) }

      it 'does not return projects with the project feature disabled' do
        is_expected.not_to include(project)
      end
    end

    shared_examples 'feature public' do
      let(:project) { create(:project, :public, :merge_requests_public) }

      it 'returns projects with the project feature public' do
        is_expected.to include(project)
      end
    end

    shared_examples 'feature enabled' do
      let(:project) { create(:project, :public, :merge_requests_enabled) }

      it 'returns projects with the project feature enabled' do
        is_expected.to include(project)
      end
    end

    shared_examples 'feature access level is nil' do
      let(:project) { create(:project, :public) }

      it 'returns projects with the project feature access level nil' do
        project.project_feature.update(merge_requests_access_level: nil)

        is_expected.to include(project)
      end
    end
3715

3716
    context 'with user' do
3717 3718 3719 3720
      before do
        project.add_guest(user)
      end

3721 3722 3723 3724 3725 3726 3727 3728
      it_behaves_like 'feature disabled'
      it_behaves_like 'feature public'
      it_behaves_like 'feature enabled'
      it_behaves_like 'feature access level is nil'

      context 'when feature is private' do
        let(:project) { create(:project, :public, :merge_requests_private) }

3729
        context 'when user does not have access to the feature' do
3730 3731
          it 'does not return projects with the project feature private' do
            is_expected.not_to include(project)
3732 3733 3734
          end
        end

3735 3736 3737
        context 'when user has access to the feature' do
          it 'returns projects with the project feature private' do
            project.add_reporter(user)
3738 3739 3740 3741 3742

            is_expected.to include(project)
          end
        end
      end
3743
    end
3744

3745 3746
    context 'user is an admin' do
      let(:user) { create(:user, :admin) }
3747

3748 3749 3750 3751
      it_behaves_like 'feature disabled'
      it_behaves_like 'feature public'
      it_behaves_like 'feature enabled'
      it_behaves_like 'feature access level is nil'
3752

3753 3754
      context 'when feature is private' do
        let(:project) { create(:project, :public, :merge_requests_private) }
3755

3756 3757
        it 'returns projects with the project feature private' do
          is_expected.to include(project)
3758 3759 3760 3761
        end
      end
    end

3762 3763
    context 'without user' do
      let(:user) { nil }
3764

3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775
      it_behaves_like 'feature disabled'
      it_behaves_like 'feature public'
      it_behaves_like 'feature enabled'
      it_behaves_like 'feature access level is nil'

      context 'when feature is private' do
        let(:project) { create(:project, :public, :merge_requests_private) }

        it 'does not return projects with the project feature private' do
          is_expected.not_to include(project)
        end
3776 3777 3778 3779
      end
    end
  end

3780
  describe '.filter_by_feature_visibility', :enable_admin_mode do
3781 3782 3783 3784
    include_context 'ProjectPolicyTable context'
    include ProjectHelpers
    using RSpec::Parameterized::TableSyntax

3785
    let_it_be(:group) { create(:group) }
3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807
    let!(:project) { create(:project, project_level, namespace: group ) }
    let(:user) { create_user_from_membership(project, membership) }

    context 'reporter level access' do
      let(:feature) { MergeRequest }

      where(:project_level, :feature_access_level, :membership, :expected_count) do
        permission_table_for_reporter_feature_access
      end

      with_them do
        it "respects visibility" do
          update_feature_access_level(project, feature_access_level)

          expected_objects = expected_count == 1 ? [project] : []

          expect(
            described_class.filter_by_feature_visibility(feature, user)
          ).to eq(expected_objects)
        end
      end
    end
3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867

    context 'issues' do
      let(:feature) { Issue }

      where(:project_level, :feature_access_level, :membership, :expected_count) do
        permission_table_for_guest_feature_access
      end

      with_them do
        it "respects visibility" do
          update_feature_access_level(project, feature_access_level)

          expected_objects = expected_count == 1 ? [project] : []

          expect(
            described_class.filter_by_feature_visibility(feature, user)
          ).to eq(expected_objects)
        end
      end
    end

    context 'wiki' do
      let(:feature) { :wiki }

      where(:project_level, :feature_access_level, :membership, :expected_count) do
        permission_table_for_guest_feature_access
      end

      with_them do
        it "respects visibility" do
          update_feature_access_level(project, feature_access_level)

          expected_objects = expected_count == 1 ? [project] : []

          expect(
            described_class.filter_by_feature_visibility(feature, user)
          ).to eq(expected_objects)
        end
      end
    end

    context 'code' do
      let(:feature) { :repository }

      where(:project_level, :feature_access_level, :membership, :expected_count) do
        permission_table_for_guest_feature_access_and_non_private_project_only
      end

      with_them do
        it "respects visibility" do
          update_feature_access_level(project, feature_access_level)

          expected_objects = expected_count == 1 ? [project] : []

          expect(
            described_class.filter_by_feature_visibility(feature, user)
          ).to eq(expected_objects)
        end
      end
    end
3868 3869
  end

3870
  describe '.wrap_with_cte' do
3871 3872 3873 3874 3875 3876 3877 3878 3879 3880
    let!(:user) { create(:user) }

    let!(:private_project) do
      create(:project, :private, creator: user, namespace: user.namespace)
    end

    let!(:public_project) { create(:project, :public) }

    let(:projects) { described_class.all.public_or_visible_to_user(user) }

3881
    subject { described_class.wrap_with_cte(projects) }
3882 3883

    it 'wrapped query matches original' do
3884
      expect(subject.to_sql).to match(/^WITH "projects_cte" AS/)
3885 3886 3887 3888
      expect(subject).to match_array(projects)
    end
  end

3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906
  describe '#pages_available?' do
    let(:project) { create(:project, group: group) }

    subject { project.pages_available? }

    before do
      allow(Gitlab.config.pages).to receive(:enabled).and_return(true)
    end

    context 'when the project is in a top level namespace' do
      let(:group) { create(:group) }

      it { is_expected.to be(true) }
    end

    context 'when the project is in a subgroup' do
      let(:group) { create(:group, :nested) }

3907
      it { is_expected.to be(true) }
3908 3909 3910
    end
  end

3911
  describe '#remove_private_deploy_keys' do
3912
    let!(:project) { create(:project) }
3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927

    context 'for a private deploy key' do
      let!(:key) { create(:deploy_key, public: false) }
      let!(:deploy_keys_project) { create(:deploy_keys_project, deploy_key: key, project: project) }

      context 'when the key is not linked to another project' do
        it 'removes the key' do
          project.remove_private_deploy_keys

          expect(project.deploy_keys).not_to include(key)
        end
      end

      context 'when the key is linked to another project' do
        before do
3928
          another_project = create(:project)
3929 3930
          create(:deploy_keys_project, deploy_key: key, project: another_project)
        end
3931

3932 3933
        it 'does not remove the key' do
          project.remove_private_deploy_keys
3934

3935 3936 3937 3938 3939 3940 3941 3942
          expect(project.deploy_keys).to include(key)
        end
      end
    end

    context 'for a public deploy key' do
      let!(:key) { create(:deploy_key, public: true) }
      let!(:deploy_keys_project) { create(:deploy_keys_project, deploy_key: key, project: project) }
3943

3944 3945
      it 'does not remove the key' do
        project.remove_private_deploy_keys
3946

3947 3948
        expect(project.deploy_keys).to include(key)
      end
3949 3950
    end
  end
3951

3952
  describe '#remove_pages' do
3953 3954
    let(:project) { create(:project).tap { |project| project.mark_pages_as_deployed } }
    let(:pages_metadatum) { project.pages_metadatum }
3955
    let(:namespace) { project.namespace }
3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966
    let(:pages_path) { project.pages_path }

    around do |example|
      FileUtils.mkdir_p(pages_path)
      begin
        example.run
      ensure
        FileUtils.rm_rf(pages_path)
      end
    end

3967
    it 'removes the pages directory and marks the project as not having pages deployed' do
3968 3969 3970 3971
      expect_any_instance_of(Projects::UpdatePagesConfigurationService).to receive(:execute)
      expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project).and_return(true)
      expect(PagesWorker).to receive(:perform_in).with(5.minutes, :remove, namespace.full_path, anything)

3972
      expect { project.remove_pages }.to change { pages_metadatum.reload.deployed }.from(true).to(false)
3973 3974 3975 3976 3977
    end

    it 'is run when the project is destroyed' do
      expect(project).to receive(:remove_pages).and_call_original

3978
      expect { project.destroy }.not_to raise_error
3979
    end
3980 3981
  end

3982
  describe '#remove_export' do
3983 3984
    let(:project) { create(:project, :with_export) }

3985 3986 3987 3988 3989 3990
    before do
      allow_next_instance_of(ProjectExportWorker) do |job|
        allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
      end
    end

3991
    it 'removes the export' do
3992 3993
      project.remove_exports

J
James Lopez 已提交
3994
      expect(project.export_file_exists?).to be_falsey
3995 3996 3997
    end
  end

3998 3999 4000 4001
  describe '#forks_count' do
    it 'returns the number of forks' do
      project = build(:project)

F
Francisco Lopez 已提交
4002
      expect_any_instance_of(Projects::ForksCountService).to receive(:count).and_return(1)
4003 4004 4005 4006

      expect(project.forks_count).to eq(1)
    end
  end
4007

4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034
  describe '#git_transfer_in_progress?' do
    let(:project) { build(:project) }

    subject { project.git_transfer_in_progress? }

    it 'returns false when repo_reference_count and wiki_reference_count are 0' do
      allow(project).to receive(:repo_reference_count) { 0 }
      allow(project).to receive(:wiki_reference_count) { 0 }

      expect(subject).to be_falsey
    end

    it 'returns true when repo_reference_count is > 0' do
      allow(project).to receive(:repo_reference_count) { 2 }
      allow(project).to receive(:wiki_reference_count) { 0 }

      expect(subject).to be_truthy
    end

    it 'returns true when wiki_reference_count is > 0' do
      allow(project).to receive(:repo_reference_count) { 0 }
      allow(project).to receive(:wiki_reference_count) { 2 }

      expect(subject).to be_truthy
    end
  end

4035
  context 'legacy storage' do
4036
    let_it_be(:project) { create(:project, :repository, :legacy_storage) }
4037
    let(:gitlab_shell) { Gitlab::Shell.new }
4038
    let(:project_storage) { project.send(:storage) }
4039

4040 4041
    before do
      allow(project).to receive(:gitlab_shell).and_return(gitlab_shell)
4042
      stub_application_setting(hashed_storage_enabled: false)
4043 4044
    end

4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056
    describe '#base_dir' do
      it 'returns base_dir based on namespace only' do
        expect(project.base_dir).to eq(project.namespace.full_path)
      end
    end

    describe '#disk_path' do
      it 'returns disk_path based on namespace and project path' do
        expect(project.disk_path).to eq("#{project.namespace.full_path}/#{project.path}")
      end
    end

4057 4058
    describe '#legacy_storage?' do
      it 'returns true when storage_version is nil' do
4059
        project = build(:project, storage_version: nil)
4060 4061 4062

        expect(project.legacy_storage?).to be_truthy
      end
4063 4064 4065 4066 4067 4068 4069 4070 4071 4072

      it 'returns true when the storage_version is 0' do
        project = build(:project, storage_version: 0)

        expect(project.legacy_storage?).to be_truthy
      end
    end

    describe '#hashed_storage?' do
      it 'returns false' do
4073
        expect(project.hashed_storage?(:repository)).to be_falsey
4074
      end
4075 4076
    end

4077 4078 4079 4080 4081
    describe '#pages_path' do
      it 'returns a path where pages are stored' do
        expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path))
      end
    end
4082 4083

    describe '#migrate_to_hashed_storage!' do
G
Gabriel Mazetto 已提交
4084 4085
      let(:project) { create(:project, :empty_repo, :legacy_storage) }

4086 4087 4088 4089
      it 'returns true' do
        expect(project.migrate_to_hashed_storage!).to be_truthy
      end

G
Gabriel Mazetto 已提交
4090 4091
      it 'does not run validation' do
        expect(project).not_to receive(:valid?)
4092 4093 4094 4095

        project.migrate_to_hashed_storage!
      end

4096
      it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the project repo is in use' do
4097
        Gitlab::ReferenceCounter.new(Gitlab::GlRepository::PROJECT.identifier_for_container(project)).increase
4098

4099
        expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in)
4100 4101 4102 4103

        project.migrate_to_hashed_storage!
      end

4104
      it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the wiki repo is in use' do
4105
        Gitlab::ReferenceCounter.new(Gitlab::GlRepository::WIKI.identifier_for_container(project)).increase
4106

4107
        expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in)
4108 4109 4110 4111

        project.migrate_to_hashed_storage!
      end

4112 4113
      it 'schedules HashedStorage::ProjectMigrateWorker' do
        expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_async).with(project.id)
4114 4115 4116 4117

        project.migrate_to_hashed_storage!
      end
    end
4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131

    describe '#rollback_to_legacy_storage!' do
      let(:project) { create(:project, :empty_repo, :legacy_storage) }

      it 'returns nil' do
        expect(project.rollback_to_legacy_storage!).to be_nil
      end

      it 'does not run validations' do
        expect(project).not_to receive(:valid?)

        project.rollback_to_legacy_storage!
      end
    end
4132 4133 4134
  end

  context 'hashed storage' do
4135
    let_it_be(:project) { create(:project, :repository, skip_disk_validation: true) }
4136
    let(:gitlab_shell) { Gitlab::Shell.new }
4137
    let(:hash) { Digest::SHA2.hexdigest(project.id.to_s) }
N
Nick Thomas 已提交
4138 4139
    let(:hashed_prefix) { File.join('@hashed', hash[0..1], hash[2..3]) }
    let(:hashed_path) { File.join(hashed_prefix, hash) }
4140

4141 4142 4143 4144 4145 4146 4147
    describe '#legacy_storage?' do
      it 'returns false' do
        expect(project.legacy_storage?).to be_falsey
      end
    end

    describe '#hashed_storage?' do
4148 4149
      it 'returns true if rolled out' do
        expect(project.hashed_storage?(:attachments)).to be_truthy
4150 4151
      end

4152 4153
      it 'returns false when not rolled out yet' do
        project.storage_version = 1
4154

4155
        expect(project.hashed_storage?(:attachments)).to be_falsey
4156 4157 4158
      end
    end

4159 4160
    describe '#base_dir' do
      it 'returns base_dir based on hash of project id' do
N
Nick Thomas 已提交
4161
        expect(project.base_dir).to eq(hashed_prefix)
4162 4163 4164 4165
      end
    end

    describe '#disk_path' do
4166
      it 'returns disk_path based on hash of project id' do
4167 4168 4169 4170
        expect(project.disk_path).to eq(hashed_path)
      end
    end

4171 4172 4173 4174 4175
    describe '#pages_path' do
      it 'returns a path where pages are stored' do
        expect(project.pages_path).to eq(File.join(Settings.pages.path, project.namespace.full_path, project.path))
      end
    end
4176 4177

    describe '#migrate_to_hashed_storage!' do
G
Gabriel Mazetto 已提交
4178 4179
      let(:project) { create(:project, :repository, skip_disk_validation: true) }

4180 4181 4182 4183
      it 'returns nil' do
        expect(project.migrate_to_hashed_storage!).to be_nil
      end

T
Toon Claes 已提交
4184
      it 'does not flag as read-only' do
4185 4186
        expect { project.migrate_to_hashed_storage! }.not_to change { project.repository_read_only }
      end
4187 4188

      context 'when partially migrated' do
G
Gabriel Mazetto 已提交
4189
        it 'enqueues a job' do
4190 4191
          project = create(:project, storage_version: 1, skip_disk_validation: true)

G
Gabriel Mazetto 已提交
4192
          Sidekiq::Testing.fake! do
4193
            expect { project.migrate_to_hashed_storage! }.to change(HashedStorage::ProjectMigrateWorker.jobs, :size).by(1)
G
Gabriel Mazetto 已提交
4194
          end
4195 4196
        end
      end
4197
    end
4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217

    describe '#rollback_to_legacy_storage!' do
      let(:project) { create(:project, :repository, skip_disk_validation: true) }

      it 'returns true' do
        expect(project.rollback_to_legacy_storage!).to be_truthy
      end

      it 'does not run validations' do
        expect(project).not_to receive(:valid?)

        project.rollback_to_legacy_storage!
      end

      it 'does not flag as read-only' do
        expect { project.rollback_to_legacy_storage! }.not_to change { project.repository_read_only }
      end

      it 'enqueues a job' do
        Sidekiq::Testing.fake! do
4218
          expect { project.rollback_to_legacy_storage! }.to change(HashedStorage::ProjectRollbackWorker.jobs, :size).by(1)
4219 4220 4221
        end
      end
    end
4222 4223
  end

4224
  describe '#has_ci?' do
4225
    let_it_be(:project, reload: true) { create(:project) }
4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246
    let(:repository) { double }

    before do
      expect(project).to receive(:repository) { repository }
    end

    context 'when has .gitlab-ci.yml' do
      before do
        expect(repository).to receive(:gitlab_ci_yml) { 'content' }
      end

      it "CI is available" do
        expect(project).to have_ci
      end
    end

    context 'when there is no .gitlab-ci.yml' do
      before do
        expect(repository).to receive(:gitlab_ci_yml) { nil }
      end

4247 4248
      it "CI is available" do
        expect(project).to have_ci
4249 4250
      end

4251
      context 'when auto devops is disabled' do
4252
        before do
4253
          stub_application_setting(auto_devops_enabled: false)
4254 4255
        end

4256 4257
        it "CI is not available" do
          expect(project).not_to have_ci
4258 4259 4260 4261 4262 4263
        end
      end
    end
  end

  describe '#auto_devops_enabled?' do
4264
    before do
4265
      Feature.enable_percentage_of_actors(:force_autodevops_on_by_default, 0)
4266 4267
    end

4268
    let_it_be(:project, reload: true) { create(:project) }
4269 4270 4271

    subject { project.auto_devops_enabled? }

4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287
    context 'when explicitly enabled' do
      before do
        create(:project_auto_devops, project: project)
      end

      it { is_expected.to be_truthy }
    end

    context 'when explicitly disabled' do
      before do
        create(:project_auto_devops, project: project, enabled: false)
      end

      it { is_expected.to be_falsey }
    end

4288 4289 4290 4291 4292
    context 'when enabled in settings' do
      before do
        stub_application_setting(auto_devops_enabled: true)
      end

4293
      it { is_expected.to be_truthy }
4294 4295 4296 4297 4298 4299 4300 4301
    end

    context 'when disabled in settings' do
      before do
        stub_application_setting(auto_devops_enabled: false)
      end

      it { is_expected.to be_falsey }
4302 4303 4304 4305 4306 4307

      context 'when explicitly enabled' do
        before do
          create(:project_auto_devops, project: project)
        end

4308
        it { is_expected.to be_truthy }
4309 4310 4311 4312
      end

      context 'when explicitly disabled' do
        before do
4313
          create(:project_auto_devops, :disabled, project: project)
4314 4315
        end

4316
        it { is_expected.to be_falsey }
4317 4318 4319
      end
    end

4320 4321 4322 4323 4324 4325 4326
    context 'when force_autodevops_on_by_default is enabled for the project' do
      it { is_expected.to be_truthy }
    end

    context 'with group parents' do
      let(:instance_enabled) { true }

4327
      before do
4328 4329
        stub_application_setting(auto_devops_enabled: instance_enabled)
        project.update!(namespace: parent_group)
4330 4331
      end

4332 4333
      context 'when enabled on parent' do
        let(:parent_group) { create(:group, :auto_devops_enabled) }
4334

4335 4336
        context 'when auto devops instance enabled' do
          it { is_expected.to be_truthy }
4337 4338
        end

4339 4340 4341 4342 4343
        context 'when auto devops instance disabled' do
          let(:instance_disabled) { false }

          it { is_expected.to be_truthy }
        end
4344 4345
      end

4346 4347 4348 4349 4350
      context 'when disabled on parent' do
        let(:parent_group) { create(:group, :auto_devops_disabled) }

        context 'when auto devops instance enabled' do
          it { is_expected.to be_falsy }
4351
        end
4352

4353 4354 4355 4356 4357 4358 4359
        context 'when auto devops instance disabled' do
          let(:instance_disabled) { false }

          it { is_expected.to be_falsy }
        end
      end

4360
      context 'when enabled on root parent' do
4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379
        let(:parent_group) { create(:group, parent: create(:group, :auto_devops_enabled)) }

        context 'when auto devops instance enabled' do
          it { is_expected.to be_truthy }
        end

        context 'when auto devops instance disabled' do
          let(:instance_disabled) { false }

          it { is_expected.to be_truthy }
        end

        context 'when explicitly disabled on parent' do
          let(:parent_group) { create(:group, :auto_devops_disabled, parent: create(:group, :auto_devops_enabled)) }

          it { is_expected.to be_falsy }
        end
      end

4380
      context 'when disabled on root parent' do
4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397
        let(:parent_group) { create(:group, parent: create(:group, :auto_devops_disabled)) }

        context 'when auto devops instance enabled' do
          it { is_expected.to be_falsy }
        end

        context 'when auto devops instance disabled' do
          let(:instance_disabled) { false }

          it { is_expected.to be_falsy }
        end

        context 'when explicitly disabled on parent' do
          let(:parent_group) { create(:group, :auto_devops_disabled, parent: create(:group, :auto_devops_enabled)) }

          it { is_expected.to be_falsy }
        end
4398 4399 4400 4401
      end
    end
  end

4402
  describe '#has_auto_devops_implicitly_enabled?' do
4403
    let_it_be(:project, reload: true) { create(:project) }
4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443

    context 'when disabled in settings' do
      before do
        stub_application_setting(auto_devops_enabled: false)
      end

      it 'does not have auto devops implicitly disabled' do
        expect(project).not_to have_auto_devops_implicitly_enabled
      end
    end

    context 'when enabled in settings' do
      before do
        stub_application_setting(auto_devops_enabled: true)
      end

      it 'auto devops is implicitly disabled' do
        expect(project).to have_auto_devops_implicitly_enabled
      end

      context 'when explicitly disabled' do
        before do
          create(:project_auto_devops, project: project, enabled: false)
        end

        it 'does not have auto devops implicitly disabled' do
          expect(project).not_to have_auto_devops_implicitly_enabled
        end
      end

      context 'when explicitly enabled' do
        before do
          create(:project_auto_devops, project: project, enabled: true)
        end

        it 'does not have auto devops implicitly disabled' do
          expect(project).not_to have_auto_devops_implicitly_enabled
        end
      end
    end
4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460

    context 'when enabled on group' do
      it 'has auto devops implicitly enabled' do
        project.update(namespace: create(:group, :auto_devops_enabled))

        expect(project).to have_auto_devops_implicitly_enabled
      end
    end

    context 'when enabled on parent group' do
      it 'has auto devops implicitly enabled' do
        subgroup = create(:group, parent: create(:group, :auto_devops_enabled))
        project.update(namespace: subgroup)

        expect(project).to have_auto_devops_implicitly_enabled
      end
    end
4461 4462
  end

4463
  describe '#has_auto_devops_implicitly_disabled?' do
4464
    let_it_be(:project, reload: true) { create(:project) }
4465

4466
    before do
4467
      Feature.enable_percentage_of_actors(:force_autodevops_on_by_default, 0)
4468 4469
    end

4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488
    context 'when explicitly disabled' do
      before do
        create(:project_auto_devops, project: project, enabled: false)
      end

      it 'does not have auto devops implicitly disabled' do
        expect(project).not_to have_auto_devops_implicitly_disabled
      end
    end

    context 'when explicitly enabled' do
      before do
        create(:project_auto_devops, project: project, enabled: true)
      end

      it 'does not have auto devops implicitly disabled' do
        expect(project).not_to have_auto_devops_implicitly_disabled
      end
    end
4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508

    context 'when enabled in settings' do
      before do
        stub_application_setting(auto_devops_enabled: true)
      end

      it 'does not have auto devops implicitly disabled' do
        expect(project).not_to have_auto_devops_implicitly_disabled
      end
    end

    context 'when disabled in settings' do
      before do
        stub_application_setting(auto_devops_enabled: false)
      end

      it 'auto devops is implicitly disabled' do
        expect(project).to have_auto_devops_implicitly_disabled
      end

4509 4510
      context 'when force_autodevops_on_by_default is enabled for the project' do
        before do
4511 4512
          create(:project_auto_devops, project: project, enabled: false)

4513
          Feature.enable_percentage_of_actors(:force_autodevops_on_by_default, 100)
4514 4515 4516 4517 4518 4519 4520
        end

        it 'does not have auto devops implicitly disabled' do
          expect(project).not_to have_auto_devops_implicitly_disabled
        end
      end

4521 4522 4523
      context 'when disabled on group' do
        it 'has auto devops implicitly disabled' do
          project.update!(namespace: create(:group, :auto_devops_disabled))
4524

4525
          expect(project).to have_auto_devops_implicitly_disabled
4526 4527 4528
        end
      end

4529 4530 4531 4532
      context 'when disabled on parent group' do
        it 'has auto devops implicitly disabled' do
          subgroup = create(:group, parent: create(:group, :auto_devops_disabled))
          project.update!(namespace: subgroup)
4533

4534
          expect(project).to have_auto_devops_implicitly_disabled
4535 4536 4537 4538 4539
        end
      end
    end
  end

4540
  describe '#api_variables' do
4541
    let_it_be(:project) { create(:project) }
4542 4543 4544 4545 4546 4547

    it 'exposes API v4 URL' do
      expect(project.api_variables.first[:key]).to eq 'CI_API_V4_URL'
      expect(project.api_variables.first[:value]).to include '/api/v4'
    end

4548
    it 'contains a URL variable for every supported API version' do
4549 4550 4551
      # Ensure future API versions have proper variables defined. We're not doing this for v3.
      supported_versions = API::API.versions - ['v3']
      supported_versions = supported_versions.select do |version|
4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563
        API::API.routes.select { |route| route.version == version }.many?
      end

      required_variables = supported_versions.map do |version|
        "CI_API_#{version.upcase}_URL"
      end

      expect(project.api_variables.map { |variable| variable[:key] })
        .to contain_exactly(*required_variables)
    end
  end

4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590
  describe '#latest_successful_builds_for' do
    let(:project) { build(:project) }

    before do
      allow(project).to receive(:default_branch).and_return('master')
    end

    context 'without a ref' do
      it 'returns a pipeline for the default branch' do
        expect(project)
          .to receive(:latest_successful_pipeline_for_default_branch)

        project.latest_successful_pipeline_for
      end
    end

    context 'with the ref set to the default branch' do
      it 'returns a pipeline for the default branch' do
        expect(project)
          .to receive(:latest_successful_pipeline_for_default_branch)

        project.latest_successful_pipeline_for(project.default_branch)
      end
    end

    context 'with a ref that is not the default branch' do
      it 'returns the latest successful pipeline for the given ref' do
4591
        expect(project.ci_pipelines).to receive(:latest_successful_for_ref).with('foo')
4592 4593 4594 4595 4596 4597

        project.latest_successful_pipeline_for('foo')
      end
    end
  end

S
Stan Hu 已提交
4598
  describe '#check_repository_path_availability' do
4599 4600 4601 4602
    let(:project) { build(:project, :repository, :legacy_storage) }

    context 'when the repository already exists' do
      let(:project) { create(:project, :repository, :legacy_storage) }
S
Stan Hu 已提交
4603

4604 4605 4606
      it 'returns false when repository already exists' do
        expect(project.check_repository_path_availability).to be_falsey
      end
4607
    end
S
Stan Hu 已提交
4608

4609
    context 'when the repository does not exist' do
4610 4611 4612
      it 'returns false when repository already exists' do
        expect(project.check_repository_path_availability).to be_truthy
      end
4613 4614 4615 4616 4617

      it 'skips gitlab-shell exists?' do
        project.skip_disk_validation = true

        expect(project.gitlab_shell).not_to receive(:repository_exists?)
4618
        expect(project.check_repository_path_availability).to be_truthy
4619
      end
S
Stan Hu 已提交
4620 4621 4622
    end
  end

4623 4624 4625 4626 4627 4628 4629 4630 4631 4632
  describe '#latest_successful_pipeline_for_default_branch' do
    let(:project) { build(:project) }

    before do
      allow(project).to receive(:default_branch).and_return('master')
    end

    it 'memoizes and returns the latest successful pipeline for the default branch' do
      pipeline = double(:pipeline)

4633
      expect(project.ci_pipelines).to receive(:latest_successful_for_ref)
4634 4635 4636 4637 4638 4639 4640 4641 4642 4643
        .with(project.default_branch)
        .and_return(pipeline)
        .once

      2.times do
        expect(project.latest_successful_pipeline_for_default_branch)
          .to eq(pipeline)
      end
    end
  end
4644 4645

  describe '#after_import' do
4646
    let(:project) { create(:project) }
4647
    let(:import_state) { create(:import_state, project: project) }
4648 4649

    it 'runs the correct hooks' do
4650 4651
      expect(project.repository).to receive(:expire_content_cache)
      expect(project.wiki.repository).to receive(:expire_content_cache)
4652
      expect(import_state).to receive(:finish)
4653
      expect(project).to receive(:update_project_counter_caches)
4654
      expect(project).to receive(:after_create_default_branch)
4655
      expect(project).to receive(:refresh_markdown_cache!)
4656
      expect(InternalId).to receive(:flush_records!).with(project: project)
4657
      expect(DetectRepositoryLanguagesWorker).to receive(:perform_async).with(project.id)
4658 4659 4660

      project.after_import
    end
4661 4662 4663 4664

    context 'branch protection' do
      let(:project) { create(:project, :repository) }

4665 4666 4667 4668
      before do
        create(:import_state, :started, project: project)
      end

4669
      it 'does not protect when branch protection is disabled' do
4670
        expect(project.namespace).to receive(:default_branch_protection).and_return(Gitlab::Access::PROTECTION_NONE)
4671 4672 4673 4674 4675 4676 4677

        project.after_import

        expect(project.protected_branches).to be_empty
      end

      it "gives developer access to push when branch protection is set to 'developers can push'" do
4678
        expect(project.namespace).to receive(:default_branch_protection).and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
4679 4680 4681 4682 4683 4684 4685 4686 4687

        project.after_import

        expect(project.protected_branches).not_to be_empty
        expect(project.default_branch).to eq(project.protected_branches.first.name)
        expect(project.protected_branches.first.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER])
      end

      it "gives developer access to merge when branch protection is set to 'developers can merge'" do
4688
        expect(project.namespace).to receive(:default_branch_protection).and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701

        project.after_import

        expect(project.protected_branches).not_to be_empty
        expect(project.default_branch).to eq(project.protected_branches.first.name)
        expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::DEVELOPER])
      end

      it 'protects default branch' do
        project.after_import

        expect(project.protected_branches).not_to be_empty
        expect(project.default_branch).to eq(project.protected_branches.first.name)
4702 4703
        expect(project.protected_branches.first.push_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
        expect(project.protected_branches.first.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
4704 4705
      end
    end
4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723
  end

  describe '#update_project_counter_caches' do
    let(:project) { create(:project) }

    it 'updates all project counter caches' do
      expect_any_instance_of(Projects::OpenIssuesCountService)
        .to receive(:refresh_cache)
        .and_call_original

      expect_any_instance_of(Projects::OpenMergeRequestsCountService)
        .to receive(:refresh_cache)
        .and_call_original

      project.update_project_counter_caches
    end
  end

4724
  describe '#write_repository_config' do
4725
    let_it_be(:project) { create(:project, :repository) }
4726 4727 4728 4729

    it 'writes full path in .git/config when key is missing' do
      project.write_repository_config

4730
      expect(rugged_config['gitlab.fullpath']).to eq project.full_path
4731 4732 4733 4734 4735
    end

    it 'updates full path in .git/config when key is present' do
      project.write_repository_config(gl_full_path: 'old/path')

4736
      expect { project.write_repository_config }.to change { rugged_config['gitlab.fullpath'] }.from('old/path').to(project.full_path)
4737 4738 4739 4740 4741 4742 4743 4744
    end

    it 'does not raise an error with an empty repository' do
      project = create(:project_empty_repo)

      expect { project.write_repository_config }.not_to raise_error
    end
  end
4745

4746 4747 4748 4749 4750 4751 4752 4753
  describe '#to_ability_name' do
    it 'returns project' do
      project = build(:project_empty_repo)

      expect(project.to_ability_name).to eq('project')
    end
  end

4754
  describe '#execute_hooks' do
4755
    let(:data) { { ref: 'refs/heads/master', data: 'data' } }
4756

D
Duana Saskia 已提交
4757
    it 'executes active projects hooks with the specified scope' do
4758 4759 4760 4761
      hook = create(:project_hook, merge_requests_events: false, push_events: true)
      expect(ProjectHook).to receive(:select_active)
        .with(:push_hooks, data)
        .and_return([hook])
4762
      project = create(:project, hooks: [hook])
4763 4764 4765

      expect_any_instance_of(ProjectHook).to receive(:async_execute).once

4766
      project.execute_hooks(data, :push_hooks)
4767 4768 4769
    end

    it 'does not execute project hooks that dont match the specified scope' do
4770
      hook = create(:project_hook, merge_requests_events: true, push_events: false)
4771 4772 4773 4774
      project = create(:project, hooks: [hook])

      expect_any_instance_of(ProjectHook).not_to receive(:async_execute).once

4775
      project.execute_hooks(data, :push_hooks)
4776 4777
    end

D
Duana Saskia 已提交
4778
    it 'does not execute project hooks which are not active' do
4779 4780 4781 4782
      hook = create(:project_hook, push_events: true)
      expect(ProjectHook).to receive(:select_active)
        .with(:push_hooks, data)
        .and_return([])
D
Duana Saskia 已提交
4783 4784 4785 4786
      project = create(:project, hooks: [hook])

      expect_any_instance_of(ProjectHook).not_to receive(:async_execute).once

4787
      project.execute_hooks(data, :push_hooks)
4788 4789 4790
    end

    it 'executes the system hooks with the specified scope' do
4791
      expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with(data, :merge_request_hooks)
4792

4793
      project = build(:project)
4794
      project.execute_hooks(data, :merge_request_hooks)
4795
    end
4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808

    it 'executes the system hooks when inside a transaction' do
      allow_any_instance_of(WebHookService).to receive(:execute)

      create(:system_hook, merge_requests_events: true)

      project = build(:project)

      # Ideally, we'd test that `WebHookWorker.jobs.size` increased by 1,
      # but since the entire spec run takes place in a transaction, we never
      # actually get to the `after_commit` hook that queues these jobs.
      expect do
        project.transaction do
4809
          project.execute_hooks(data, :merge_request_hooks)
4810 4811 4812
        end
      end.not_to raise_error # Sidekiq::Worker::EnqueueFromTransactionError
    end
4813
  end
4814

4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840
  describe '#execute_services' do
    let(:service) { create(:slack_service, push_events: true, merge_requests_events: false, active: true) }

    it 'executes services with the specified scope' do
      data = 'any data'

      expect(SlackService).to receive(:allocate).and_wrap_original do |method|
        method.call.tap do |instance|
          expect(instance).to receive(:async_execute).with(data).once
        end
      end

      service.project.execute_services(data, :push_hooks)
    end

    it 'does not execute services that don\'t match the specified scope' do
      expect(SlackService).not_to receive(:allocate).and_wrap_original do |method|
        method.call.tap do |instance|
          expect(instance).not_to receive(:async_execute)
        end
      end

      service.project.execute_services(anything, :merge_request_hooks)
    end
  end

S
Stan Hu 已提交
4841
  describe '#has_active_hooks?' do
4842
    let_it_be(:project) { create(:project) }
S
Stan Hu 已提交
4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857

    it { expect(project.has_active_hooks?).to be_falsey }

    it 'returns true when a matching push hook exists' do
      create(:project_hook, push_events: true, project: project)

      expect(project.has_active_hooks?(:merge_request_events)).to be_falsey
      expect(project.has_active_hooks?).to be_truthy
    end

    it 'returns true when a matching system hook exists' do
      create(:system_hook, push_events: true)

      expect(project.has_active_hooks?(:merge_request_events)).to be_falsey
      expect(project.has_active_hooks?).to be_truthy
4858 4859 4860
    end

    it 'returns true when a plugin exists' do
4861
      expect(Gitlab::FileHook).to receive(:any?).twice.and_return(true)
4862 4863 4864

      expect(project.has_active_hooks?(:merge_request_events)).to be_truthy
      expect(project.has_active_hooks?).to be_truthy
S
Stan Hu 已提交
4865 4866 4867 4868
    end
  end

  describe '#has_active_services?' do
4869
    let_it_be(:project) { create(:project) }
S
Stan Hu 已提交
4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880

    it { expect(project.has_active_services?).to be_falsey }

    it 'returns true when a matching service exists' do
      create(:custom_issue_tracker_service, push_events: true, merge_requests_events: false, project: project)

      expect(project.has_active_services?(:merge_request_hooks)).to be_falsey
      expect(project.has_active_services?).to be_truthy
    end
  end

4881 4882
  describe '#badges' do
    let(:project_group) { create(:group) }
4883
    let(:project) { create(:project, path: 'avatar', namespace: project_group) }
4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896

    before do
      create_list(:project_badge, 2, project: project)
      create(:group_badge, group: project_group)
    end

    it 'returns the project and the project group badges' do
      create(:group_badge, group: create(:group))

      expect(Badge.count).to eq 4
      expect(project.badges.count).to eq 3
    end

4897 4898
    context 'with nested_groups' do
      let(:parent_group) { create(:group) }
4899

4900 4901 4902 4903
      before do
        create_list(:group_badge, 2, group: project_group)
        project_group.update(parent: parent_group)
      end
4904

4905 4906
      it 'returns the project and the project nested groups badges' do
        expect(project.badges.count).to eq 5
4907 4908 4909
      end
    end
  end
4910

4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942
  context 'with cross internal project merge requests' do
    let(:project) { create(:project, :repository, :internal) }
    let(:forked_project) { fork_project(project, nil, repository: true) }
    let(:user) { double(:user) }

    it "does not endlessly loop for internal projects with MRs to each other", :sidekiq_inline do
      allow(user).to receive(:can?).and_return(true, false, true)
      allow(user).to receive(:id).and_return(1)

      create(
        :merge_request,
        target_project: project,
        target_branch: 'merge-test',
        source_project: forked_project,
        source_branch: 'merge-test',
        allow_collaboration: true
      )

      create(
        :merge_request,
        target_project: forked_project,
        target_branch: 'merge-test',
        source_project: project,
        source_branch: 'merge-test',
        allow_collaboration: true
      )

      expect(user).to receive(:can?).at_most(5).times
      project.branch_allows_collaboration?(user, "merge-test")
    end
  end

4943 4944
  context 'with cross project merge requests' do
    let(:user) { create(:user) }
4945 4946
    let(:target_project) { create(:project, :repository) }
    let(:project) { fork_project(target_project, nil, repository: true) }
4947 4948 4949 4950 4951 4952 4953 4954 4955 4956
    let!(:local_merge_request) do
      create(
        :merge_request,
        target_project: project,
        target_branch: 'target-branch',
        source_project: project,
        source_branch: 'awesome-feature-1',
        allow_collaboration: true
      )
    end
4957 4958 4959 4960
    let!(:merge_request) do
      create(
        :merge_request,
        target_project: target_project,
4961
        target_branch: 'target-branch',
4962 4963
        source_project: project,
        source_branch: 'awesome-feature-1',
4964
        allow_collaboration: true
4965 4966 4967 4968
      )
    end

    before do
4969
      target_project.add_developer(user)
4970 4971
    end

4972 4973 4974 4975
    describe '#merge_requests_allowing_push_to_user' do
      it 'returns open merge requests for which the user has developer access to the target project' do
        expect(project.merge_requests_allowing_push_to_user(user)).to include(merge_request)
      end
4976

4977 4978
      it 'does not include closed merge requests' do
        merge_request.close
4979

4980 4981 4982 4983 4984 4985
        expect(project.merge_requests_allowing_push_to_user(user)).to be_empty
      end

      it 'does not include merge requests for guest users' do
        guest = create(:user)
        target_project.add_guest(guest)
4986

4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998
        expect(project.merge_requests_allowing_push_to_user(guest)).to be_empty
      end

      it 'does not include the merge request for other users' do
        other_user = create(:user)

        expect(project.merge_requests_allowing_push_to_user(other_user)).to be_empty
      end

      it 'is empty when no user is passed' do
        expect(project.merge_requests_allowing_push_to_user(nil)).to be_empty
      end
4999 5000
    end

5001
    describe '#any_branch_allows_collaboration?' do
5002
      it 'allows access when there are merge requests open allowing collaboration', :sidekiq_might_not_need_inline do
5003
        expect(project.any_branch_allows_collaboration?(user))
5004 5005 5006
          .to be_truthy
      end

5007 5008 5009 5010 5011 5012 5013 5014 5015
      it 'does not allow access when there are no merge requests open allowing collaboration' do
        merge_request.close!

        expect(project.any_branch_allows_collaboration?(user))
          .to be_falsey
      end
    end

    describe '#branch_allows_collaboration?' do
5016
      it 'allows access if the user can merge the merge request', :sidekiq_might_not_need_inline do
5017
        expect(project.branch_allows_collaboration?(user, 'awesome-feature-1'))
5018 5019 5020
          .to be_truthy
      end

5021 5022 5023 5024
      it 'does not allow guest users access' do
        guest = create(:user)
        target_project.add_guest(guest)

5025
        expect(project.branch_allows_collaboration?(guest, 'awesome-feature-1'))
5026 5027 5028
          .to be_falsy
      end

5029
      it 'does not allow access to branches for which the merge request was closed' do
5030 5031
        create(:merge_request, :closed,
               target_project: target_project,
5032
               target_branch: 'target-branch',
5033 5034
               source_project: project,
               source_branch: 'rejected-feature-1',
5035
               allow_collaboration: true)
5036

5037
        expect(project.branch_allows_collaboration?(user, 'rejected-feature-1'))
5038 5039 5040
          .to be_falsy
      end

5041
      it 'does not allow access if the user cannot merge the merge request' do
5042
        create(:protected_branch, :maintainers_can_push, project: target_project, name: 'target-branch')
5043

5044
        expect(project.branch_allows_collaboration?(user, 'awesome-feature-1'))
5045 5046 5047
          .to be_falsy
      end

5048
      context 'when the requeststore is active', :request_store do
5049
        it 'only queries per project across instances' do
5050
          control = ActiveRecord::QueryRecorder.new { project.branch_allows_collaboration?(user, 'awesome-feature-1') }
5051

5052
          expect { 2.times { described_class.find(project.id).branch_allows_collaboration?(user, 'awesome-feature-1') } }
5053
            .not_to exceed_query_limit(control).with_threshold(2)
5054
        end
5055 5056 5057
      end
    end
  end
R
Rob Watson 已提交
5058

5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077
  describe '#external_authorization_classification_label' do
    it 'falls back to the default when none is configured' do
      enable_external_authorization_service_check

      expect(build(:project).external_authorization_classification_label)
        .to eq('default_label')
    end

    it 'returns the classification label if it was configured on the project' do
      enable_external_authorization_service_check

      project = build(:project,
                      external_authorization_classification_label: 'hello')

      expect(project.external_authorization_classification_label)
        .to eq('hello')
    end
  end

R
Rob Watson 已提交
5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121
  describe "#pages_https_only?" do
    subject { build(:project) }

    context "when HTTPS pages are disabled" do
      it { is_expected.not_to be_pages_https_only }
    end

    context "when HTTPS pages are enabled", :https_pages_enabled do
      it { is_expected.to be_pages_https_only }
    end
  end

  describe "#pages_https_only? validation", :https_pages_enabled do
    subject(:project) do
      # set-up dirty object:
      create(:project, pages_https_only: false).tap do |p|
        p.pages_https_only = true
      end
    end

    context "when no domains are associated" do
      it { is_expected.to be_valid }
    end

    context "when domains including keys and certificates are associated" do
      before do
        allow(project)
          .to receive(:pages_domains)
          .and_return([instance_double(PagesDomain, https?: true)])
      end

      it { is_expected.to be_valid }
    end

    context "when domains including no keys or certificates are associated" do
      before do
        allow(project)
          .to receive(:pages_domains)
          .and_return([instance_double(PagesDomain, https?: false)])
      end

      it { is_expected.not_to be_valid }
    end
  end
5122

5123
  describe '#toggle_ci_cd_settings!' do
5124
    it 'toggles the value on #settings' do
5125
      project = create(:project, group_runners_enabled: false)
5126 5127 5128

      expect(project.group_runners_enabled).to be false

5129
      project.toggle_ci_cd_settings!(:group_runners_enabled)
5130 5131 5132 5133

      expect(project.group_runners_enabled).to be true
    end
  end
5134

5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151
  describe '#gitlab_deploy_token' do
    let(:project) { create(:project) }

    subject { project.gitlab_deploy_token }

    context 'when there is a gitlab deploy token associated' do
      let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, projects: [project]) }

      it { is_expected.to eq(deploy_token) }
    end

    context 'when there is no a gitlab deploy token associated' do
      it { is_expected.to be_nil }
    end

    context 'when there is a gitlab deploy token associated but is has been revoked' do
      let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, :revoked, projects: [project]) }
5152

5153 5154 5155
      it { is_expected.to be_nil }
    end

5156
    context 'when there is a gitlab deploy token associated but it is expired' do
5157 5158 5159 5160
      let!(:deploy_token) { create(:deploy_token, :gitlab_deploy_token, :expired, projects: [project]) }

      it { is_expected.to be_nil }
    end
5161

5162
    context 'when there is a deploy token associated with a different name' do
5163 5164 5165 5166
      let!(:deploy_token) { create(:deploy_token, projects: [project]) }

      it { is_expected.to be_nil }
    end
5167 5168 5169 5170 5171 5172 5173

    context 'when there is a deploy token associated to a different project' do
      let(:project_2) { create(:project) }
      let!(:deploy_token) { create(:deploy_token, projects: [project_2]) }

      it { is_expected.to be_nil }
    end
5174
  end
J
Jan Provaznik 已提交
5175 5176

  context 'with uploads' do
5177
    it_behaves_like 'model with uploads', true do
J
Jan Provaznik 已提交
5178 5179 5180 5181 5182
      let(:model_object) { create(:project, :with_avatar) }
      let(:upload_attribute) { :avatar }
      let(:uploader_class) { AttachmentUploader }
    end
  end
5183

5184
  describe '#members_among' do
5185
    let(:users) { create_list(:user, 3) }
5186

5187 5188
    let_it_be(:group) { create(:group) }
    let_it_be(:project) { create(:project, namespace: group) }
5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225

    before do
      project.add_guest(users.first)
      project.group.add_maintainer(users.last)
    end

    context 'when users is an Array' do
      it 'returns project members among the users' do
        expect(project.members_among(users)).to eq([users.first, users.last])
      end

      it 'maintains input order' do
        expect(project.members_among(users.reverse)).to eq([users.last, users.first])
      end

      it 'returns empty array if users is empty' do
        result = project.members_among([])

        expect(result).to be_empty
      end
    end

    context 'when users is a relation' do
      it 'returns project members among the users' do
        result = project.members_among(User.where(id: users.map(&:id)))

        expect(result).to be_a(ActiveRecord::Relation)
        expect(result).to eq([users.first, users.last])
      end

      it 'returns empty relation if users is empty' do
        result = project.members_among(User.none)

        expect(result).to be_a(ActiveRecord::Relation)
        expect(result).to be_empty
      end
    end
5226
  end
5227

5228
  describe '#find_or_initialize_services' do
5229
    it 'returns only enabled services' do
5230
      allow(Service).to receive(:available_services_names).and_return(%w[prometheus pushover teamcity])
5231
      allow(subject).to receive(:disabled_services).and_return(%w[prometheus])
5232

5233
      services = subject.find_or_initialize_services
5234

5235 5236
      expect(services.count).to eq(2)
      expect(services.map(&:title)).to eq(['JetBrains TeamCity CI', 'Pushover'])
5237 5238
    end
  end
5239

5240
  describe '#find_or_initialize_service' do
5241
    it 'avoids N+1 database queries' do
5242
      allow(Service).to receive(:available_services_names).and_return(%w[prometheus pushover])
5243 5244 5245 5246 5247 5248 5249 5250

      control_count = ActiveRecord::QueryRecorder.new { subject.find_or_initialize_service('prometheus') }.count

      allow(Service).to receive(:available_services_names).and_call_original

      expect { subject.find_or_initialize_service('prometheus') }.not_to exceed_query_limit(control_count)
    end

5251 5252
    it 'returns nil if integration is disabled' do
      allow(subject).to receive(:disabled_services).and_return(%w[prometheus])
5253 5254

      expect(subject.find_or_initialize_service('prometheus')).to be_nil
5255
    end
5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295

    context 'with an existing integration' do
      subject { create(:project) }

      before do
        create(:prometheus_service, project: subject, api_url: 'https://prometheus.project.com/')
      end

      it 'retrieves the integration' do
        expect(subject.find_or_initialize_service('prometheus').api_url).to eq('https://prometheus.project.com/')
      end
    end

    context 'with an instance-level and template integrations' do
      before do
        create(:prometheus_service, :instance, api_url: 'https://prometheus.instance.com/')
        create(:prometheus_service, :template, api_url: 'https://prometheus.template.com/')
      end

      it 'builds the service from the instance if exists' do
        expect(subject.find_or_initialize_service('prometheus').api_url).to eq('https://prometheus.instance.com/')
      end
    end

    context 'with an instance-level and template integrations' do
      before do
        create(:prometheus_service, :template, api_url: 'https://prometheus.template.com/')
      end

      it 'builds the service from the template if instance does not exists' do
        expect(subject.find_or_initialize_service('prometheus').api_url).to eq('https://prometheus.template.com/')
      end
    end

    context 'without an exisiting integration, nor instance-level or template' do
      it 'builds the service if instance or template does not exists' do
        expect(subject.find_or_initialize_service('prometheus')).to be_a(PrometheusService)
        expect(subject.find_or_initialize_service('prometheus').api_url).to be_nil
      end
    end
5296 5297
  end

5298 5299 5300 5301 5302 5303 5304 5305 5306
  describe '.for_group' do
    it 'returns the projects for a given group' do
      group = create(:group)
      project = create(:project, namespace: group)

      expect(described_class.for_group(group)).to eq([project])
    end
  end

S
Shinya Maeda 已提交
5307 5308 5309
  describe '.deployments' do
    subject { project.deployments }

5310
    let(:project) { create(:project, :repository) }
S
Shinya Maeda 已提交
5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340

    before do
      allow_any_instance_of(Deployment).to receive(:create_ref)
    end

    context 'when there is a deployment record with created status' do
      let(:deployment) { create(:deployment, :created, project: project) }

      it 'does not return the record' do
        is_expected.to be_empty
      end
    end

    context 'when there is a deployment record with running status' do
      let(:deployment) { create(:deployment, :running, project: project) }

      it 'does not return the record' do
        is_expected.to be_empty
      end
    end

    context 'when there is a deployment record with success status' do
      let(:deployment) { create(:deployment, :success, project: project) }

      it 'returns the record' do
        is_expected.to eq([deployment])
      end
    end
  end

5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362
  describe '#snippets_visible?' do
    it 'returns true when a logged in user can read snippets' do
      project = create(:project, :public)
      user = create(:user)

      expect(project.snippets_visible?(user)).to eq(true)
    end

    it 'returns true when an anonymous user can read snippets' do
      project = create(:project, :public)

      expect(project.snippets_visible?).to eq(true)
    end

    it 'returns false when a user can not read snippets' do
      project = create(:project, :private)
      user = create(:user)

      expect(project.snippets_visible?(user)).to eq(false)
    end
  end

T
Thong Kuah 已提交
5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381
  describe '#all_clusters' do
    let(:project) { create(:project) }
    let(:cluster) { create(:cluster, cluster_type: :project_type, projects: [project]) }

    subject { project.all_clusters }

    it 'returns project level cluster' do
      expect(subject).to eq([cluster])
    end

    context 'project belongs to a group' do
      let(:group_cluster) { create(:cluster, :group) }
      let(:group) { group_cluster.group }
      let(:project) { create(:project, group: group) }

      it 'returns clusters for groups of this project' do
        expect(subject).to contain_exactly(cluster, group_cluster)
      end
    end
5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392

    context 'project is hosted on instance with integrated cluster' do
      let(:group_cluster) { create(:cluster, :group) }
      let(:instance_cluster) { create(:cluster, :instance) }
      let(:group) { group_cluster.group }
      let(:project) { create(:project, group: group) }

      it 'returns all available clusters for this project' do
        expect(subject).to contain_exactly(cluster, group_cluster, instance_cluster)
      end
    end
T
Thong Kuah 已提交
5393 5394
  end

5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409
  describe '#object_pool_params' do
    let(:project) { create(:project, :repository, :public) }

    subject { project.object_pool_params }

    context 'when the objects cannot be pooled' do
      let(:project) { create(:project, :repository, :private) }

      it { is_expected.to be_empty }
    end

    context 'when a pool is created' do
      it 'returns that pool repository' do
        expect(subject).not_to be_empty
        expect(subject[:pool_repository]).to be_persisted
5410 5411

        expect(project.reload.pool_repository).to eq(subject[:pool_repository])
5412 5413 5414 5415
      end
    end
  end

5416 5417
  describe '#git_objects_poolable?' do
    subject { project }
5418

5419 5420
    context 'when not using hashed storage' do
      let(:project) { create(:project, :legacy_storage, :public, :repository) }
5421 5422 5423 5424

      it { is_expected.not_to be_git_objects_poolable }
    end

5425
    context 'when the project is private' do
5426
      let(:project) { create(:project, :private) }
5427

5428 5429
      it { is_expected.not_to be_git_objects_poolable }
    end
5430

5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442
    context 'when the project is public' do
      let(:project) { create(:project, :repository, :public) }

      it { is_expected.to be_git_objects_poolable }
    end

    context 'when the project is internal' do
      let(:project) { create(:project, :repository, :internal) }

      it { is_expected.to be_git_objects_poolable }
    end

5443 5444
    context 'when objects are poolable' do
      let(:project) { create(:project, :repository, :public) }
5445

5446
      it { is_expected.to be_git_objects_poolable }
5447 5448 5449
    end
  end

5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460
  describe '#leave_pool_repository' do
    let(:pool) { create(:pool_repository) }
    let(:project) { create(:project, :repository, pool_repository: pool) }

    it 'removes the membership' do
      project.leave_pool_repository

      expect(pool.member_projects.reload).not_to include(project)
    end
  end

5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529
  describe '#check_personal_projects_limit' do
    context 'when creating a project for a group' do
      it 'does nothing' do
        creator = build(:user)
        project = build(:project, namespace: build(:group), creator: creator)

        allow(creator)
          .to receive(:can_create_project?)
          .and_return(false)

        project.check_personal_projects_limit

        expect(project.errors).to be_empty
      end
    end

    context 'when the user is not allowed to create a personal project' do
      let(:user) { build(:user) }
      let(:project) { build(:project, creator: user) }

      before do
        allow(user)
          .to receive(:can_create_project?)
          .and_return(false)
      end

      context 'when the project limit is zero' do
        it 'adds a validation error' do
          allow(user)
            .to receive(:projects_limit)
            .and_return(0)

          project.check_personal_projects_limit

          expect(project.errors[:limit_reached].first)
            .to match(/Personal project creation is not allowed/)
        end
      end

      context 'when the project limit is greater than zero' do
        it 'adds a validation error' do
          allow(user)
            .to receive(:projects_limit)
            .and_return(5)

          project.check_personal_projects_limit

          expect(project.errors[:limit_reached].first)
            .to match(/Your project limit is 5 projects/)
        end
      end
    end

    context 'when the user is allowed to create personal projects' do
      it 'does nothing' do
        user = build(:user)
        project = build(:project, creator: user)

        allow(user)
          .to receive(:can_create_project?)
          .and_return(true)

        project.check_personal_projects_limit

        expect(project.errors).to be_empty
      end
    end
  end

5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558
  context 'pages deployed' do
    let(:project) { create(:project) }

    {
      mark_pages_as_deployed: true,
      mark_pages_as_not_deployed: false
    }.each do |method_name, flag|
      describe method_name do
        it "creates new record and sets deployed to #{flag} if none exists yet" do
          project.pages_metadatum.destroy!
          project.reload

          project.send(method_name)

          expect(project.pages_metadatum.reload.deployed).to eq(flag)
        end

        it "updates the existing record and sets deployed to #{flag}" do
          pages_metadatum = project.pages_metadatum
          pages_metadatum.update!(deployed: !flag)

          expect { project.send(method_name) }.to change {
            pages_metadatum.reload.deployed
          }.from(!flag).to(flag)
        end
      end
    end
  end

5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573
  describe '#has_pool_repsitory?' do
    it 'returns false when it does not have a pool repository' do
      subject = create(:project, :repository)

      expect(subject.has_pool_repository?).to be false
    end

    it 'returns true when it has a pool repository' do
      pool    = create(:pool_repository, :ready)
      subject = create(:project, :repository, pool_repository: pool)

      expect(subject.has_pool_repository?).to be true
    end
  end

5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593
  describe '#access_request_approvers_to_be_notified' do
    it 'returns a maximum of ten, active, non_requested maintainers of the project in recent_sign_in descending order' do
      group = create(:group, :public)
      project = create(:project, group: group)

      users = create_list(:user, 12, :with_sign_ins)
      active_maintainers = users.map do |user|
        create(:project_member, :maintainer, user: user)
      end

      create(:project_member, :maintainer, :blocked, project: project)
      create(:project_member, :developer, project: project)
      create(:project_member, :access_request, :maintainer, project: project)

      active_maintainers_in_recent_sign_in_desc_order = project.members_and_requesters.where(id: active_maintainers).order_recent_sign_in.limit(10)

      expect(project.access_request_approvers_to_be_notified).to eq(active_maintainers_in_recent_sign_in_desc_order)
    end
  end

5594 5595 5596 5597 5598
  describe '#pages_lookup_path' do
    let(:pages_domain) { build(:pages_domain) }
    let(:project) { build(:project) }

    it 'returns instance of Pages::LookupPath' do
5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614
      expect(Pages::LookupPath).to receive(:new).with(project, domain: pages_domain, trim_prefix: 'mygroup').and_call_original

      expect(project.pages_lookup_path(domain: pages_domain, trim_prefix: 'mygroup')).to be_a(Pages::LookupPath)
    end
  end

  describe '.with_pages_deployed' do
    it 'returns only projects that have pages deployed' do
      _project_without_pages = create(:project)
      project_with_pages = create(:project)
      project_with_pages.mark_pages_as_deployed

      expect(described_class.with_pages_deployed).to contain_exactly(project_with_pages)
    end
  end

5615 5616 5617 5618 5619 5620 5621 5622 5623 5624
  describe '.pages_metadata_not_migrated' do
    it 'returns only projects that have pages deployed' do
      _project_with_pages_metadata_migrated = create(:project)
      project_with_pages_metadata_not_migrated = create(:project)
      project_with_pages_metadata_not_migrated.pages_metadatum.destroy!

      expect(described_class.pages_metadata_not_migrated).to contain_exactly(project_with_pages_metadata_not_migrated)
    end
  end

5625 5626 5627 5628 5629 5630 5631 5632 5633 5634
  describe '#pages_group_root?' do
    it 'returns returns true if pages_url is same as pages_group_url' do
      project = build(:project)
      expect(project).to receive(:pages_url).and_return(project.pages_group_url)

      expect(project.pages_group_root?).to eq(true)
    end

    it 'returns returns false if pages_url is different than pages_group_url' do
      project = build(:project)
5635

5636
      expect(project.pages_group_root?).to eq(false)
5637 5638 5639
    end
  end

5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686
  describe '#closest_setting' do
    using RSpec::Parameterized::TableSyntax

    shared_examples_for 'fetching closest setting' do
      let!(:namespace) { create(:namespace) }
      let!(:project) { create(:project, namespace: namespace) }

      let(:setting_name) { :some_setting }
      let(:setting) { project.closest_setting(setting_name) }

      before do
        allow(project).to receive(:read_attribute).with(setting_name).and_return(project_setting)
        allow(namespace).to receive(:closest_setting).with(setting_name).and_return(group_setting)
        allow(Gitlab::CurrentSettings).to receive(setting_name).and_return(global_setting)
      end

      it 'returns closest non-nil value' do
        expect(setting).to eq(result)
      end
    end

    context 'when setting is of non-boolean type' do
      where(:global_setting, :group_setting, :project_setting, :result) do
        100 | 200 | 300 | 300
        100 | 200 | nil | 200
        100 | nil | nil | 100
        nil | nil | nil | nil
      end

      with_them do
        it_behaves_like 'fetching closest setting'
      end
    end

    context 'when setting is of boolean type' do
      where(:global_setting, :group_setting, :project_setting, :result) do
        true | true  | false | false
        true | false | nil   | false
        true | nil   | nil   | true
      end

      with_them do
        it_behaves_like 'fetching closest setting'
      end
    end
  end

5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 5706 5707 5708 5709 5710 5711 5712 5713 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723 5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737 5738 5739 5740 5741
  describe '#drop_visibility_level!' do
    context 'when has a group' do
      let(:group) { create(:group, visibility_level: group_visibility_level) }
      let(:project) { build(:project, namespace: group, visibility_level: project_visibility_level) }

      context 'when the group `visibility_level` is more strict' do
        let(:group_visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
        let(:project_visibility_level) { Gitlab::VisibilityLevel::INTERNAL }

        it 'sets `visibility_level` value from the group' do
          expect { project.drop_visibility_level! }
            .to change { project.visibility_level }
            .to(Gitlab::VisibilityLevel::PRIVATE)
        end
      end

      context 'when the group `visibility_level` is less strict' do
        let(:group_visibility_level) { Gitlab::VisibilityLevel::INTERNAL }
        let(:project_visibility_level) { Gitlab::VisibilityLevel::PRIVATE }

        it 'does not change the value of the `visibility_level` field' do
          expect { project.drop_visibility_level! }
            .not_to change { project.visibility_level }
        end
      end
    end

    context 'when `restricted_visibility_levels` of the GitLab instance exist' do
      before do
        stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
      end

      let(:project) { build(:project, visibility_level: project_visibility_level) }

      context 'when `visibility_level` is included into `restricted_visibility_levels`' do
        let(:project_visibility_level) { Gitlab::VisibilityLevel::INTERNAL }

        it 'sets `visibility_level` value to `PRIVATE`' do
          expect { project.drop_visibility_level! }
            .to change { project.visibility_level }
            .to(Gitlab::VisibilityLevel::PRIVATE)
        end
      end

      context 'when `restricted_visibility_levels` does not include `visibility_level`' do
        let(:project_visibility_level) { Gitlab::VisibilityLevel::PUBLIC }

        it 'does not change the value of the `visibility_level` field' do
          expect { project.drop_visibility_level! }
            .to not_change { project.visibility_level }
        end
      end
    end
  end

5742 5743 5744 5745 5746 5747 5748 5749 5750 5751 5752 5753 5754 5755
  describe 'with services and chat names' do
    subject { create(:project) }

    let(:service) { create(:service, project: subject) }

    before do
      create_list(:chat_name, 5, service: service)
    end

    it 'removes chat names on removal' do
      expect { subject.destroy }.to change { ChatName.count }.by(-5)
    end
  end

5756 5757 5758 5759 5760 5761 5762 5763 5764 5765 5766 5767 5768 5769 5770 5771 5772 5773 5774
  describe 'with_issues_or_mrs_available_for_user' do
    before do
      Project.delete_all
    end

    it 'returns correct projects' do
      user = create(:user)
      project1 = create(:project, :public, :merge_requests_disabled, :issues_enabled)
      project2 = create(:project, :public, :merge_requests_disabled, :issues_disabled)
      project3 = create(:project, :public, :issues_enabled, :merge_requests_enabled)
      project4 = create(:project, :private, :issues_private, :merge_requests_private)

      [project1, project2, project3, project4].each { |project| project.add_developer(user) }

      expect(described_class.with_issues_or_mrs_available_for_user(user))
        .to contain_exactly(project1, project3, project4)
    end
  end

5775 5776 5777 5778 5779 5780 5781 5782
  describe '#limited_protected_branches' do
    let(:project) { create(:project) }
    let!(:protected_branch) { create(:protected_branch, project: project) }
    let!(:another_protected_branch) { create(:protected_branch, project: project) }

    subject { project.limited_protected_branches(1) }

    it 'returns limited number of protected branches based on specified limit' do
5783
      expect(subject.count).to eq(1)
5784 5785 5786
    end
  end

5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833
  describe '#all_lfs_objects_oids' do
    let(:project) { create(:project) }
    let(:lfs_object) { create(:lfs_object) }
    let(:another_lfs_object) { create(:lfs_object) }

    subject { project.all_lfs_objects_oids }

    context 'when project has associated LFS objects' do
      before do
        create(:lfs_objects_project, lfs_object: lfs_object, project: project)
        create(:lfs_objects_project, lfs_object: another_lfs_object, project: project)
      end

      it 'returns OIDs of LFS objects' do
        expect(subject).to match_array([lfs_object.oid, another_lfs_object.oid])
      end

      context 'and there are specified oids' do
        subject { project.all_lfs_objects_oids(oids: [lfs_object.oid]) }

        it 'returns OIDs of LFS objects that match specified oids' do
          expect(subject).to eq([lfs_object.oid])
        end
      end
    end

    context 'when fork has associated LFS objects to itself and source' do
      let(:source) { create(:project) }
      let(:project) { fork_project(source) }

      before do
        create(:lfs_objects_project, lfs_object: lfs_object, project: source)
        create(:lfs_objects_project, lfs_object: another_lfs_object, project: project)
      end

      it 'returns OIDs of LFS objects' do
        expect(subject).to match_array([lfs_object.oid, another_lfs_object.oid])
      end
    end

    context 'when project has no associated LFS objects' do
      it 'returns empty array' do
        expect(subject).to be_empty
      end
    end
  end

5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849
  describe '#lfs_objects_oids' do
    let(:project) { create(:project) }
    let(:lfs_object) { create(:lfs_object) }
    let(:another_lfs_object) { create(:lfs_object) }

    subject { project.lfs_objects_oids }

    context 'when project has associated LFS objects' do
      before do
        create(:lfs_objects_project, lfs_object: lfs_object, project: project)
        create(:lfs_objects_project, lfs_object: another_lfs_object, project: project)
      end

      it 'returns OIDs of LFS objects' do
        expect(subject).to match_array([lfs_object.oid, another_lfs_object.oid])
      end
5850 5851 5852 5853 5854 5855 5856 5857

      context 'and there are specified oids' do
        subject { project.lfs_objects_oids(oids: [lfs_object.oid]) }

        it 'returns OIDs of LFS objects that match specified oids' do
          expect(subject).to eq([lfs_object.oid])
        end
      end
5858 5859 5860 5861 5862 5863 5864 5865 5866
    end

    context 'when project has no associated LFS objects' do
      it 'returns empty array' do
        expect(subject).to be_empty
      end
    end
  end

5867 5868 5869 5870 5871
  describe '#alerts_service_activated?' do
    let!(:project) { create(:project) }

    subject { project.alerts_service_activated? }

5872 5873 5874 5875 5876 5877 5878 5879 5880 5881 5882 5883 5884 5885 5886
    context 'when project has an activated alerts service' do
      before do
        create(:alerts_service, project: project)
      end

      it { is_expected.to be_truthy }
    end

    context 'when project has an inactive alerts service' do
      before do
        create(:alerts_service, :inactive, project: project)
      end

      it { is_expected.to be_falsey }
    end
5887 5888
  end

5889 5890 5891 5892 5893 5894 5895 5896 5897 5898 5899 5900 5901 5902 5903 5904 5905 5906 5907 5908 5909 5910 5911 5912
  describe '#prometheus_service_active?' do
    let(:project) { create(:project) }

    subject { project.prometheus_service_active? }

    before do
      create(:prometheus_service, project: project, manual_configuration: manual_configuration)
    end

    context 'when project has an activated prometheus service' do
      let(:manual_configuration) { true }

      it { is_expected.to be_truthy }
    end

    context 'when project has an inactive prometheus service' do
      let(:manual_configuration) { false }

      it 'the service is marked as inactive' do
        expect(subject).to be_falsey
      end
    end
  end

5913 5914 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 5930
  describe '#self_monitoring?' do
    let_it_be(:project) { create(:project) }

    subject { project.self_monitoring? }

    context 'when the project is instance self monitoring' do
      before do
        stub_application_setting(self_monitoring_project_id: project.id)
      end

      it { is_expected.to be true }
    end

    context 'when the project is not self monitoring' do
      it { is_expected.to be false }
    end
  end

5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 5992 5993 5994 5995 5996 5997 5998 5999 6000 6001 6002 6003 6004 6005
  describe '#add_export_job' do
    context 'if not already present' do
      it 'starts project export job' do
        user = create(:user)
        project = build(:project)

        expect(ProjectExportWorker).to receive(:perform_async).with(user.id, project.id, nil, {})

        project.add_export_job(current_user: user)
      end
    end
  end

  describe '#export_in_progress?' do
    let(:project) { build(:project) }
    let!(:project_export_job ) { create(:project_export_job, project: project) }

    context 'when project export is enqueued' do
      it { expect(project.export_in_progress?).to be false }
    end

    context 'when project export is in progress' do
      before do
        project_export_job.start!
      end

      it { expect(project.export_in_progress?).to be true }
    end

    context 'when project export is completed' do
      before do
        finish_job(project_export_job)
      end

      it { expect(project.export_in_progress?).to be false }
    end
  end

  describe '#export_status' do
    let(:project) { build(:project) }
    let!(:project_export_job ) { create(:project_export_job, project: project) }

    context 'when project export is enqueued' do
      it { expect(project.export_status).to eq :queued }
    end

    context 'when project export is in progress' do
      before do
        project_export_job.start!
      end

      it { expect(project.export_status).to eq :started }
    end

    context 'when project export is completed' do
      before do
        finish_job(project_export_job)
        allow(project).to receive(:export_file).and_return(double(ImportExportUploader, file: 'exists.zip'))
      end

      it { expect(project.export_status).to eq :finished }
    end

    context 'when project export is being regenerated' do
      let!(:new_project_export_job ) { create(:project_export_job, project: project) }

      before do
        finish_job(project_export_job)
        allow(project).to receive(:export_file).and_return(double(ImportExportUploader, file: 'exists.zip'))
      end

      it { expect(project.export_status).to eq :regeneration_in_progress }
    end
  end

6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023
  describe '#environments_for_scope' do
    let_it_be(:project, reload: true) { create(:project) }

    before do
      create_list(:environment, 2, project: project)
    end

    it 'retrieves all project environments when using the * wildcard' do
      expect(project.environments_for_scope("*")).to eq(project.environments)
    end

    it 'retrieves a specific project environment when using the name of that environment' do
      environment = project.environments.first

      expect(project.environments_for_scope(environment.name)).to eq([environment])
    end
  end

6024 6025 6026 6027 6028 6029 6030 6031 6032 6033 6034 6035 6036 6037 6038 6039 6040 6041 6042 6043 6044 6045 6046 6047 6048 6049 6050 6051
  describe '#latest_jira_import' do
    let_it_be(:project) { create(:project) }
    context 'when no jira imports' do
      it 'returns nil' do
        expect(project.latest_jira_import).to be nil
      end
    end

    context 'when single jira import' do
      let!(:jira_import1) { create(:jira_import_state, project: project) }

      it 'returns the jira import' do
        expect(project.latest_jira_import).to eq(jira_import1)
      end
    end

    context 'when multiple jira imports' do
      let!(:jira_import1) { create(:jira_import_state, :finished, created_at: 1.day.ago, project: project) }
      let!(:jira_import2) { create(:jira_import_state, :failed, created_at: 2.days.ago, project: project) }
      let!(:jira_import3) { create(:jira_import_state, :started, created_at: 3.days.ago, project: project) }

      it 'returns latest jira import by created_at' do
        expect(project.jira_imports.pluck(:id)).to eq([jira_import3.id, jira_import2.id, jira_import1.id])
        expect(project.latest_jira_import).to eq(jira_import1)
      end
    end
  end

6052 6053 6054 6055 6056 6057 6058 6059 6060 6061 6062 6063 6064 6065 6066 6067 6068 6069 6070 6071 6072 6073
  describe '#design_management_enabled?' do
    let(:project) { build(:project) }

    where(:lfs_enabled, :hashed_storage_enabled, :expectation) do
      false | false | false
      true  | false | false
      false | true  | false
      true  | true  | true
    end

    with_them do
      before do
        expect(project).to receive(:lfs_enabled?).and_return(lfs_enabled)
        allow(project).to receive(:hashed_storage?).with(:repository).and_return(hashed_storage_enabled)
      end

      it do
        expect(project.design_management_enabled?).to be(expectation)
      end
    end
  end

6074 6075 6076 6077 6078 6079 6080 6081 6082 6083 6084 6085 6086 6087 6088 6089 6090
  describe '#bots' do
    subject { project.bots }

    let_it_be(:project) { create(:project) }
    let_it_be(:project_bot) { create(:user, :project_bot) }
    let_it_be(:user) { create(:user) }

    before_all do
      [project_bot, user].each do |member|
        project.add_maintainer(member)
      end
    end

    it { is_expected.to contain_exactly(project_bot) }
    it { is_expected.not_to include(user) }
  end

6091 6092 6093 6094 6095 6096 6097 6098
  describe "#metrics_setting" do
    let(:project) { build(:project) }

    it 'creates setting if it does not exist' do
      expect(project.metrics_setting).to be_an_instance_of(ProjectMetricsSetting)
    end
  end

6099 6100 6101 6102 6103
  def finish_job(export_job)
    export_job.start
    export_job.finish
  end

6104
  def rugged_config
6105
    rugged_repo(project.repository).config
6106
  end
6107 6108 6109 6110 6111 6112 6113 6114 6115 6116 6117 6118 6119 6120

  def create_pipeline(project, status = 'success')
    create(:ci_pipeline, project: project,
                         sha: project.commit.sha,
                         ref: project.default_branch,
                         status: status)
  end

  def create_build(new_pipeline = pipeline, name = 'test')
    create(:ci_build, :success, :artifacts,
           pipeline: new_pipeline,
           status: new_pipeline.status,
           name: name)
  end
G
gitlabhq 已提交
6121
end