require 'spec_helper' describe Project do describe 'associations' do it { is_expected.to delegate_method(:shared_runners_minutes).to(:statistics) } it { is_expected.to delegate_method(:shared_runners_seconds).to(:statistics) } it { is_expected.to delegate_method(:shared_runners_seconds_last_reset).to(:statistics) } it { is_expected.to delegate_method(:actual_shared_runners_minutes_limit).to(:namespace) } it { is_expected.to delegate_method(:shared_runners_minutes_limit_enabled?).to(:namespace) } it { is_expected.to delegate_method(:shared_runners_minutes_used?).to(:namespace) } it { is_expected.to have_one(:mirror_data).class_name('ProjectMirrorData') } it { is_expected.to have_many(:path_locks) } it { is_expected.to have_many(:sourced_pipelines) } it { is_expected.to have_many(:source_pipelines) } end describe '#push_rule' do let(:project) { create(:empty_project, push_rule: create(:push_rule)) } subject(:push_rule) { project.push_rule(true) } it { is_expected.not_to be_nil } context 'push rules unlicensed' do before do stub_licensed_features(push_rules: false) end it { is_expected.to be_nil } end end describe "#execute_hooks" do context "group hooks" do let(:group) { create(:group) } let(:project) { create(:empty_project, namespace: group) } let(:group_hook) { create(:group_hook, group: group, push_events: true) } it 'executes the hook when the feature is enabled' do stub_licensed_features(group_webhooks: true) fake_service = double expect(WebHookService).to receive(:new) .with(group_hook, { some: 'info' }, 'push_hooks') { fake_service } expect(fake_service).to receive(:async_execute) project.execute_hooks(some: 'info') end it 'does not execute the hook when the feature is disabled' do stub_licensed_features(group_webhooks: false) expect(WebHookService).not_to receive(:new) .with(group_hook, { some: 'info' }, 'push_hooks') project.execute_hooks(some: 'info') end end end describe '#execute_hooks' do it "triggers project and group hooks" do group = create :group, name: 'gitlab' project = create(:empty_project, name: 'gitlabhq', namespace: group) project_hook = create(:project_hook, push_events: true, project: project) group_hook = create(:group_hook, push_events: true, group: group) stub_request(:post, project_hook.url) stub_request(:post, group_hook.url) expect_any_instance_of(GroupHook).to receive(:async_execute).and_return(true) expect_any_instance_of(ProjectHook).to receive(:async_execute).and_return(true) project.execute_hooks({}, :push_hooks) end end describe '#allowed_to_share_with_group?' do let(:project) { create(:empty_project) } it "returns true" do expect(project.allowed_to_share_with_group?).to be_truthy end it "returns false" do project.namespace.update(share_with_group_lock: true) expect(project.allowed_to_share_with_group?).to be_falsey end end describe '#feature_available?' do let(:namespace) { build_stubbed(:namespace) } let(:project) { build_stubbed(:empty_project, namespace: namespace) } let(:user) { build_stubbed(:user) } subject { project.feature_available?(feature, user) } context 'when feature symbol is included on Namespace features code' do before do stub_application_setting('check_namespace_plan?' => check_namespace_plan) allow(Gitlab).to receive(:com?) { true } stub_licensed_features(feature => allowed_on_global_license) allow(namespace).to receive(:plan) { plan_license } end License::FEATURE_CODES.each do |feature_sym, feature_code| context feature_sym.to_s do let(:feature) { feature_sym } let(:feature_code) { feature_code } context "checking #{feature_sym} availability both on Global and Namespace license" do let(:check_namespace_plan) { true } context 'allowed by Plan License AND Global License' do let(:allowed_on_global_license) { true } let(:plan_license) { Namespace::GOLD_PLAN } it 'returns true' do is_expected.to eq(true) end end context 'not allowed by Plan License but project and namespace are public' do let(:allowed_on_global_license) { true } let(:plan_license) { Namespace::BRONZE_PLAN } it 'returns true' do allow(namespace).to receive(:public?) { true } allow(project).to receive(:public?) { true } is_expected.to eq(true) end end unless License.plan_includes_feature?(License::STARTER_PLAN, feature_sym) context 'not allowed by Plan License' do let(:allowed_on_global_license) { true } let(:plan_license) { Namespace::BRONZE_PLAN } it 'returns false' do is_expected.to eq(false) end end end context 'not allowed by Global License' do let(:allowed_on_global_license) { false } let(:plan_license) { Namespace::GOLD_PLAN } it 'returns false' do is_expected.to eq(false) end end end context "when checking #{feature_code} only for Global license" do let(:check_namespace_plan) { false } context 'allowed by Global License' do let(:allowed_on_global_license) { true } it 'returns true' do is_expected.to eq(true) end end context 'not allowed by Global License' do let(:allowed_on_global_license) { false } it 'returns false' do is_expected.to eq(false) end end end end end end it 'only loads licensed availability once' do expect(project).to receive(:load_licensed_feature_available) .once.and_call_original 2.times { project.feature_available?(:service_desk) } end context 'when feature symbol is not included on Namespace features code' do let(:feature) { :issues } it 'checks availability of licensed feature' do expect(project.project_feature).to receive(:feature_available?).with(feature, user) subject end end end describe '#mirror_waiting_duration' do it 'returns in seconds the time spent in the queue' do project = create(:empty_project, :mirror, :import_scheduled) mirror_data = project.mirror_data mirror_data.update_attributes(last_update_started_at: mirror_data.last_update_scheduled_at + 5.minutes) expect(project.mirror_waiting_duration).to eq(300) end end describe '#mirror_update_duration' do it 'returns in seconds the time spent updating' do project = create(:empty_project, :mirror, :import_started) project.update_attributes(mirror_last_update_at: project.mirror_data.last_update_started_at + 5.minutes) expect(project.mirror_update_duration).to eq(300) end end describe '#has_remote_mirror?' do let(:project) { create(:empty_project, :remote_mirror, :import_started) } 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 unlicensed' do stub_licensed_features(repository_mirrors: false) is_expected.to be_falsy end it 'returns false when remote mirror is disabled' do project.remote_mirrors.first.update_attributes(enabled: false) is_expected.to be_falsy end end describe '#update_remote_mirrors' do let(:project) { create(:empty_project, :remote_mirror, :import_started) } 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 unlicensed' do stub_licensed_features(repository_mirrors: false) expect_any_instance_of(RemoteMirror).not_to receive(:sync) update_remote_mirrors end it 'does not sync disabled remote mirrors' do project.remote_mirrors.first.update_attributes(enabled: false) expect_any_instance_of(RemoteMirror).not_to receive(:sync) update_remote_mirrors end end describe '#any_runners_limit' do let(:project) { create(:empty_project, shared_runners_enabled: shared_runners_enabled) } let(:specific_runner) { create(:ci_runner) } let(:shared_runner) { create(:ci_runner, :shared) } context 'for shared runners enabled' do let(:shared_runners_enabled) { true } before do shared_runner end it 'has a shared runner' do expect(project.any_runners?).to be_truthy end it 'checks the presence of shared runner' do expect(project.any_runners? { |runner| runner == shared_runner }).to be_truthy end context 'with used pipeline minutes' do let(:namespace) { create(:namespace, :with_used_build_minutes_limit) } let(:project) do create(:empty_project, namespace: namespace, shared_runners_enabled: shared_runners_enabled) end it 'does not have a shared runner' do expect(project.any_runners?).to be_falsey end end end end describe '#shared_runners_available?' do subject { project.shared_runners_available? } context 'with used pipeline minutes' do let(:namespace) { create(:namespace, :with_used_build_minutes_limit) } let(:project) do create(:empty_project, namespace: namespace, shared_runners_enabled: true) end before do expect(namespace).to receive(:shared_runners_minutes_used?).and_call_original end it 'shared runners are not available' do expect(project.shared_runners_available?).to be_falsey end end end describe '#shared_runners_minutes_limit_enabled?' do let(:project) { create(:empty_project) } subject { project.shared_runners_minutes_limit_enabled? } before do allow(project.namespace).to receive(:shared_runners_minutes_limit_enabled?) .and_return(true) end context 'with shared runners enabled' do before do project.shared_runners_enabled = true end context 'for public project' do before do project.visibility_level = Project::PUBLIC end it { is_expected.to be_falsey } end context 'for internal project' do before do project.visibility_level = Project::INTERNAL end it { is_expected.to be_truthy } end context 'for private project' do before do project.visibility_level = Project::INTERNAL end it { is_expected.to be_truthy } end end context 'without shared runners' do before do project.shared_runners_enabled = false end it { is_expected.to be_falsey } end end describe '#size_limit_enabled?' do let(:project) { create(:empty_project) } context 'when repository_size_limit is not configured' do it 'is disabled' do expect(project.size_limit_enabled?).to be_falsey end end context 'when repository_size_limit is configured' do before do project.update_attributes(repository_size_limit: 1024) end context 'with an EES license' do let!(:license) { create(:license, plan: License::STARTER_PLAN) } it 'is enabled' do expect(project.size_limit_enabled?).to be_truthy end end context 'with an EEP license' do let!(:license) { create(:license, plan: License::PREMIUM_PLAN) } it 'is enabled' do expect(project.size_limit_enabled?).to be_truthy end end context 'without a License' do before do License.destroy_all end it 'is disabled' do expect(project.size_limit_enabled?).to be_falsey end end end end describe '#service_desk_enabled?' do let!(:license) { create(:license, plan: License::PREMIUM_PLAN) } let(:namespace) { create(:namespace) } subject(:project) { build(:empty_project, :private, namespace: namespace, service_desk_enabled: true) } before do allow(::Gitlab).to receive(:com?).and_return(true) allow(::Gitlab::IncomingEmail).to receive(:enabled?).and_return(true) allow(::Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true) end it 'is enabled' do expect(project.service_desk_enabled?).to be_truthy expect(project.service_desk_enabled).to be_truthy end context 'namespace plans active' do before do stub_application_setting(check_namespace_plan: true) end it 'is disabled' do expect(project.service_desk_enabled?).to be_falsy expect(project.service_desk_enabled).to be_falsy end context 'Service Desk available in namespace plan' do let(:namespace) { create(:namespace, plan: Namespace::SILVER_PLAN) } it 'is enabled' do expect(project.service_desk_enabled?).to be_truthy expect(project.service_desk_enabled).to be_truthy end end end end describe '#service_desk_address' do let(:project) { create(:empty_project, service_desk_enabled: true) } before do allow(::EE::Gitlab::ServiceDesk).to receive(:enabled?).and_return(true) allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true) allow(Gitlab.config.incoming_email).to receive(:address).and_return("test+%{key}@mail.com") end it 'uses project full path as service desk address key' do expect(project.service_desk_address).to eq("test+#{project.full_path}@mail.com") end end describe '#secret_variables_for' do let(:project) { create(:empty_project) } let!(:secret_variable) do create(:ci_variable, value: 'secret', project: project) end let!(:protected_variable) do create(:ci_variable, :protected, value: 'protected', project: project) end subject { project.secret_variables_for(ref: 'ref') } before do stub_application_setting( default_branch_protection: Gitlab::Access::PROTECTION_NONE) end context 'when environment is specified' do let(:environment) { create(:environment, name: 'review/name') } subject do project.secret_variables_for(ref: 'ref', environment: environment) end shared_examples 'matching environment scope' do context 'when variable environment scope is available' do before do stub_licensed_features(variable_environment_scope: true) end it 'contains the secret variable' do is_expected.to contain_exactly(secret_variable) end end context 'when variable environment scope is unavailable' do before do stub_licensed_features(variable_environment_scope: false) end it 'does not contain the secret variable' do is_expected.not_to contain_exactly(secret_variable) end end end shared_examples 'not matching environment scope' do context 'when variable environment scope is available' do before do stub_licensed_features(variable_environment_scope: true) end it 'does not contain the secret variable' do is_expected.not_to contain_exactly(secret_variable) end end context 'when variable environment scope is unavailable' do before do stub_licensed_features(variable_environment_scope: false) end it 'does not contain the secret variable' do is_expected.not_to contain_exactly(secret_variable) end end end context 'when environment scope is exactly matched' do before do secret_variable.update(environment_scope: 'review/name') end it_behaves_like 'matching environment scope' end context 'when environment scope is matched by wildcard' do before do secret_variable.update(environment_scope: 'review/*') end it_behaves_like 'matching environment scope' end context 'when environment scope does not match' do before do secret_variable.update(environment_scope: 'review/*/special') end it_behaves_like 'not matching environment scope' end context 'when environment scope has _' do before do stub_licensed_features(variable_environment_scope: true) end it 'does not treat it as wildcard' do secret_variable.update(environment_scope: '*_*') is_expected.not_to contain_exactly(secret_variable) end it 'matches literally for _' do secret_variable.update(environment_scope: 'foo_bar/*') environment.update(name: 'foo_bar/test') is_expected.to contain_exactly(secret_variable) 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 before do stub_licensed_features(variable_environment_scope: true) end it 'does not treat it as wildcard' do secret_variable.update_attribute(:environment_scope, '*%*') is_expected.not_to contain_exactly(secret_variable) end it 'matches literally for _' do secret_variable.update(environment_scope: 'foo%bar/*') environment.update_attribute(:name, 'foo%bar/test') is_expected.to contain_exactly(secret_variable) end end context 'when variables with the same name have different environment scopes' do let!(:partially_matched_variable) do create(:ci_variable, key: secret_variable.key, value: 'partial', environment_scope: 'review/*', project: project) end let!(:perfectly_matched_variable) do create(:ci_variable, key: secret_variable.key, value: 'prefect', environment_scope: 'review/name', project: project) end before do stub_licensed_features(variable_environment_scope: true) end it 'puts variables matching environment scope more in the end' do is_expected.to eq( [secret_variable, partially_matched_variable, perfectly_matched_variable]) end end end end describe '#approvals_before_merge' do [ { license: true, database: 5, expected: 5 }, { license: true, database: 0, expected: 0 }, { license: false, database: 5, expected: 0 }, { license: false, database: 0, expected: 0 } ].each do |spec| context spec.inspect do let(:spec) { spec } let(:project) { build(:empty_project, approvals_before_merge: spec[:database]) } subject { project.approvals_before_merge } before do stub_licensed_features(merge_request_approvers: spec[:license]) end it { is_expected.to eq(spec[:expected]) } end end end describe "#reset_approvals_on_push?" do [ { license: true, database: true, expected: true }, { license: true, database: false, expected: false }, { license: false, database: true, expected: false }, { license: false, database: false, expected: false } ].each do |spec| context spec.inspect do let(:spec) { spec } let(:project) { build(:empty_project, reset_approvals_on_push: spec[:database]) } subject { project.reset_approvals_on_push? } before do stub_licensed_features(merge_request_approvers: spec[:license]) end it { is_expected.to eq(spec[:expected]) } end end end describe '#approvals_before_merge' do [ { license: true, database: 5, expected: 5 }, { license: true, database: 0, expected: 0 }, { license: false, database: 5, expected: 0 }, { license: false, database: 0, expected: 0 } ].each do |spec| context spec.inspect do let(:spec) { spec } let(:project) { build(:empty_project, approvals_before_merge: spec[:database]) } subject { project.approvals_before_merge } before do stub_licensed_features(merge_request_approvers: spec[:license]) end it { is_expected.to eq(spec[:expected]) } end end end describe '#merge_method' do [ { ff: true, rebase: true, ff_licensed: true, rebase_licensed: true, method: :ff }, { ff: true, rebase: true, ff_licensed: true, rebase_licensed: false, method: :ff }, { ff: true, rebase: true, ff_licensed: false, rebase_licensed: true, method: :rebase_merge }, { ff: true, rebase: true, ff_licensed: false, rebase_licensed: false, method: :merge }, { ff: true, rebase: false, ff_licensed: true, rebase_licensed: true, method: :ff }, { ff: true, rebase: false, ff_licensed: true, rebase_licensed: false, method: :ff }, { ff: true, rebase: false, ff_licensed: false, rebase_licensed: true, method: :merge }, { ff: true, rebase: false, ff_licensed: false, rebase_licensed: false, method: :merge }, { ff: false, rebase: true, ff_licensed: true, rebase_licensed: true, method: :rebase_merge }, { ff: false, rebase: true, ff_licensed: true, rebase_licensed: false, method: :merge }, { ff: false, rebase: true, ff_licensed: false, rebase_licensed: true, method: :rebase_merge }, { ff: false, rebase: true, ff_licensed: false, rebase_licensed: false, method: :merge }, { ff: false, rebase: false, ff_licensed: true, rebase_licensed: true, method: :merge }, { ff: false, rebase: false, ff_licensed: true, rebase_licensed: false, method: :merge }, { ff: false, rebase: false, ff_licensed: false, rebase_licensed: true, method: :merge }, { ff: false, rebase: false, ff_licensed: false, rebase_licensed: false, method: :merge } ].each do |spec| context spec.inspect do let(:project) { build(:empty_project, merge_requests_rebase_enabled: spec[:rebase], merge_requests_ff_only_enabled: spec[:ff]) } let(:spec) { spec } subject { project.merge_method } before do stub_licensed_features(merge_request_rebase: spec[:rebase_licensed], fast_forward_merge: spec[:ff_licensed]) end it { is_expected.to eq(spec[:method]) } end end end describe '#rename_repo' do context 'when running on a primary node' do let!(:geo_node) { create(:geo_node, :primary, :current) } let(:project) { create(:project, :repository) } let(:gitlab_shell) { Gitlab::Shell.new } before do allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) allow(project).to receive(:previous_changes).and_return('path' => ['foo']) end it 'logs the Geo::RepositoryRenamedEvent' do stub_container_registry_config(enabled: false) allow(gitlab_shell).to receive(:mv_repository).twice.and_return(true) expect(Geo::RepositoryRenamedEventStore).to receive(:new) .with(instance_of(described_class), old_path: 'foo', old_path_with_namespace: "#{project.namespace.full_path}/foo") .and_call_original expect { project.rename_repo }.to change(Geo::RepositoryRenamedEvent, :count).by(1) end end end shared_examples 'project with disabled services' do it 'has some disabled services' do expect(project.disabled_services).to match_array(disabled_services) end end shared_examples 'project without disabled services' do it 'has some disabled services' do expect(project.disabled_services).to be_empty end end describe '#disabled_services' do let(:namespace) { create(:group, :private) } let(:project) { create(:empty_project, :private, namespace: namespace) } let(:disabled_services) { %w(jenkins jenkins_deprecated) } context 'without a license key' do before do License.destroy_all end it_behaves_like 'project with disabled services' end context 'with a license key' do context 'when checking of namespace plan is enabled' do before do stub_application_setting_on_object(project, should_check_namespace_plan: true) end context 'and namespace does not have a plan' do it_behaves_like 'project with disabled services' end context 'and namespace has a plan' do let(:namespace) { create(:group, :private, plan: Namespace::BRONZE_PLAN) } it_behaves_like 'project without disabled services' end end context 'when checking of namespace plan is not enabled' do before do stub_application_setting_on_object(project, should_check_namespace_plan: false) end it_behaves_like 'project without disabled services' end end end end