diff options
| author | Achilleas Pipinellis <axil@gitlab.com> | 2019-08-20 20:22:43 +0200 |
|---|---|---|
| committer | Achilleas Pipinellis <axil@gitlab.com> | 2019-08-20 20:22:43 +0200 |
| commit | e61308ce1d82e12e5087371469baea4a452875d1 (patch) | |
| tree | 62551a3ae4eab75e5af7e3b35358c07a51b2132f /spec/models/project_spec.rb | |
| parent | 4f323bb62fbe71a4352de25cab141f361a3fe1a6 (diff) | |
| parent | 2989ed078c1d45b0959dcecb1bc3c8f4740a3c0d (diff) | |
| download | gitlab-ce-docs-patch-71.tar.gz | |
Merge branch 'master' into docs-patch-71docs-patch-71
Diffstat (limited to 'spec/models/project_spec.rb')
| -rw-r--r-- | spec/models/project_spec.rb | 491 |
1 files changed, 322 insertions, 169 deletions
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index aad08b9d4aa..ff9e94afc12 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -98,11 +98,26 @@ describe Project do it { is_expected.to have_many(:lfs_file_locks) } it { is_expected.to have_many(:project_deploy_tokens) } it { is_expected.to have_many(:deploy_tokens).through(:project_deploy_tokens) } + it { is_expected.to have_many(:cycle_analytics_stages) } it 'has an inverse relationship with merge requests' do expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project) end + 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 + context 'after initialized' do it "has a project_feature" do expect(described_class.new.project_feature).to be_present @@ -159,24 +174,6 @@ describe Project do it { is_expected.to include_module(Sortable) } end - describe '.missing_kubernetes_namespace' do - let!(:project) { create(:project) } - let!(:cluster) { create(:cluster, :provided_by_user, :group) } - let(:kubernetes_namespaces) { project.kubernetes_namespaces } - - subject { described_class.missing_kubernetes_namespace(kubernetes_namespaces) } - - it { is_expected.to contain_exactly(project) } - - context 'kubernetes namespace exists' do - before do - create(:cluster_kubernetes_namespace, project: project, cluster: cluster) - end - - it { is_expected.to be_empty } - end - end - describe 'validation' do let!(:project) { create(:project) } @@ -199,7 +196,7 @@ describe Project do .only_integer .is_greater_than_or_equal_to(10.minutes) .is_less_than(1.month) - .with_message('needs to be beetween 10 minutes and 1 month') + .with_message('needs to be between 10 minutes and 1 month') end it 'does not allow new projects beyond user limits' do @@ -483,7 +480,6 @@ describe Project do 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) } - it { is_expected.to delegate_method(:group_clusters_enabled?).to(:group).with_arguments(allow_nil: true) } it { is_expected.to delegate_method(:root_ancestor).to(:namespace).with_arguments(allow_nil: true) } it { is_expected.to delegate_method(:last_pipeline).to(:commit).with_arguments(allow_nil: true) } end @@ -1161,7 +1157,6 @@ describe Project do describe '#pipeline_for' do let(:project) { create(:project, :repository) } - let!(:pipeline) { create_pipeline(project) } shared_examples 'giving the correct pipeline' do it { is_expected.to eq(pipeline) } @@ -1173,19 +1168,47 @@ describe Project do end end - context 'with explicit sha' do - subject { project.pipeline_for('master', pipeline.sha) } + context 'with a matching pipeline' do + let!(:pipeline) { create_pipeline(project) } + + context 'with explicit sha' do + subject { project.pipeline_for('master', pipeline.sha) } + + it_behaves_like 'giving the correct pipeline' + + context 'with supplied id' do + let!(:other_pipeline) { create_pipeline(project) } + + subject { project.pipeline_for('master', pipeline.sha, other_pipeline.id) } - it_behaves_like 'giving the correct pipeline' + 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' + end end - context 'with implicit sha' do + context 'when there is no matching pipeline' do subject { project.pipeline_for('master') } - it_behaves_like 'giving the correct pipeline' + it { is_expected.to be_nil } end end + describe '#pipelines_for' do + let(:project) { create(:project, :repository) } + let!(:pipeline) { create_pipeline(project) } + let!(:other_pipeline) { create_pipeline(project) } + + subject { project.pipelines_for(project.default_branch, project.commit.sha) } + + it { is_expected.to contain_exactly(pipeline, other_pipeline) } + end + describe '#builds_enabled' do let(:project) { create(:project) } @@ -1479,11 +1502,28 @@ describe Project do end context 'when set to INTERNAL in application settings' do + using RSpec::Parameterized::TableSyntax + before do stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL) end it { is_expected.to eq(Gitlab::VisibilityLevel::INTERNAL) } + + 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 end end @@ -1645,26 +1685,6 @@ describe Project do end end - describe '.paginate_in_descending_order_using_id' do - let!(:project1) { create(:project) } - let!(:project2) { create(:project) } - - it 'orders the relation in descending order' do - expect(described_class.paginate_in_descending_order_using_id) - .to eq([project2, project1]) - end - - it 'applies a limit to the relation' do - expect(described_class.paginate_in_descending_order_using_id(limit: 1)) - .to eq([project2]) - end - - it 'limits projects by and ID when given' do - expect(described_class.paginate_in_descending_order_using_id(before: project2.id)) - .to eq([project1]) - end - end - describe '.including_namespace_and_owner' do it 'eager loads the namespace and namespace owner' do create(:project) @@ -1989,62 +2009,33 @@ describe Project do end end - describe '#latest_successful_build_for' do + describe '#latest_successful_build_for_ref' do 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') + it_behaves_like 'latest successful build for sha or ref' - expect(project.latest_successful_build_for(build1_p2.name)) - .to eq(build1_p2) - end - end - - context 'with succeeded pipeline' do - let!(:build) { create_build } + subject { project.latest_successful_build_for_ref(build_name) } - context 'standalone pipeline' do - it 'returns builds for ref for default_branch' do - expect(project.latest_successful_build_for(build.name)) - .to eq(build) - end + context 'with a specified ref' do + let(:build) { create_build } - it 'returns empty relation if the build cannot be found' do - expect(project.latest_successful_build_for('TAIL')) - .to be_nil - end - end + subject { project.latest_successful_build_for_ref(build.name, project.default_branch) } - context 'with some pending pipeline' do - before do - create_build(create_pipeline(project, 'pending')) - end - - it 'gives the latest build from latest pipeline' do - expect(project.latest_successful_build_for(build.name)) - .to eq(build) - end - end + it { is_expected.to eq(build) } end + end - context 'with pending pipeline' do - it 'returns empty relation' do - pipeline.update(status: 'pending') - pending_build = create_build(pipeline) + describe '#latest_successful_build_for_sha' do + let(:project) { create(:project, :repository) } + let(:pipeline) { create_pipeline(project) } - expect(project.latest_successful_build_for(pending_build.name)).to be_nil - end - end + it_behaves_like 'latest successful build for sha or ref' + + subject { project.latest_successful_build_for_sha(build_name, project.commit.sha) } end - describe '#latest_successful_build_for!' do + describe '#latest_successful_build_for_ref!' do let(:project) { create(:project, :repository) } let(:pipeline) { create_pipeline(project) } @@ -2057,7 +2048,7 @@ describe Project do build1_p2 = create_build(pipeline2, 'test') create_build(pipeline2, 'test2') - expect(project.latest_successful_build_for(build1_p2.name)) + expect(project.latest_successful_build_for_ref!(build1_p2.name)) .to eq(build1_p2) end end @@ -2067,12 +2058,12 @@ describe Project do context 'standalone pipeline' do it 'returns builds for ref for default_branch' do - expect(project.latest_successful_build_for!(build.name)) + expect(project.latest_successful_build_for_ref!(build.name)) .to eq(build) end it 'returns exception if the build cannot be found' do - expect { project.latest_successful_build_for!(build.name, 'TAIL') } + expect { project.latest_successful_build_for_ref!(build.name, 'TAIL') } .to raise_error(ActiveRecord::RecordNotFound) end end @@ -2083,7 +2074,7 @@ describe Project do end it 'gives the latest build from latest pipeline' do - expect(project.latest_successful_build_for!(build.name)) + expect(project.latest_successful_build_for_ref!(build.name)) .to eq(build) end end @@ -2094,7 +2085,7 @@ describe Project do pipeline.update(status: 'pending') pending_build = create_build(pipeline) - expect { project.latest_successful_build_for!(pending_build.name) } + expect { project.latest_successful_build_for_ref!(pending_build.name) } .to raise_error(ActiveRecord::RecordNotFound) end end @@ -2262,7 +2253,22 @@ describe Project do end end - describe '#ancestors_upto', :nested_groups do + 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 + + describe '#ancestors_upto' do let(:parent) { create(:group) } let(:child) { create(:group, parent: parent) } let(:child2) { create(:group, parent: child) } @@ -2301,7 +2307,7 @@ describe Project do it { is_expected.to eq(group) } end - context 'in a nested group', :nested_groups do + context 'in a nested group' do let(:root) { create(:group) } let(:child) { create(:group, parent: root) } let(:project) { create(:project, group: child) } @@ -2310,6 +2316,57 @@ describe Project do end end + 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 + + context 'when :emails_disabled feature flag is off' do + before do + stub_feature_flags(emails_disabled: false) + end + + context 'emails disabled in group' do + it 'returns false' do + allow(project.namespace).to receive(:emails_disabled?) { true } + + expect(project.emails_disabled?).to be_falsey + end + end + + context 'emails enabled in group' do + it 'returns false' do + allow(project.namespace).to receive(:emails_disabled?) { false } + project.update_attribute(:emails_disabled, true) + + expect(project.emails_disabled?).to be_falsey + end + end + end + end + describe '#lfs_enabled?' do let(:project) { create(:project) } @@ -2449,7 +2506,7 @@ describe Project do expect(forked_project.in_fork_network_of?(project)).to be_truthy end - it 'is true for a fork of a fork', :postgresql do + it 'is true for a fork of a fork' do other_fork = fork_project(forked_project) expect(other_fork.in_fork_network_of?(project)).to be_truthy @@ -2604,55 +2661,33 @@ describe Project do end describe '#deployment_variables' do - context 'when project has no deployment service' do - let(:project) { create(:project) } - - it 'returns an empty array' do - expect(project.deployment_variables).to eq [] - end - end + let(:project) { create(:project) } + let(:environment) { 'production' } - context 'when project uses mock deployment service' do - let(:project) { create(:mock_deployment_project) } + subject { project.deployment_variables(environment: environment) } - it 'returns an empty array' do - expect(project.deployment_variables).to eq [] - end + before do + expect(project).to receive(:deployment_platform).with(environment: environment) + .and_return(deployment_platform) end - context 'when project has a deployment service' do - shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do - it 'returns variables from this service' do - expect(project.deployment_variables).to include( - { key: 'KUBE_TOKEN', value: project.deployment_platform.token, public: false, masked: true } - ) - end - end + context 'when project has no deployment platform' do + let(:deployment_platform) { nil } - context 'when user configured kubernetes from Integration > Kubernetes' do - let(:project) { create(:kubernetes_project) } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end + it { is_expected.to eq [] } + end - context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has not been executed' do - let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } + context 'when project has a deployment platform' do + let(:platform_variables) { %w(platform variables) } + let(:deployment_platform) { double } - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' + before do + expect(deployment_platform).to receive(:predefined_variables) + .with(project: project, environment_name: environment) + .and_return(platform_variables) end - context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has been executed' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token) } - let!(:cluster) { kubernetes_namespace.cluster } - let(:project) { kubernetes_namespace.project } - - it 'returns token from kubernetes namespace' do - expect(project.deployment_variables).to include( - { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true } - ) - end - end + it { is_expected.to eq platform_variables } end end @@ -2680,9 +2715,10 @@ describe Project do describe '#ci_variables_for' do let(:project) { create(:project) } + let(:environment_scope) { '*' } let!(:ci_variable) do - create(:ci_variable, value: 'secret', project: project) + create(:ci_variable, value: 'secret', project: project, environment_scope: environment_scope) end let!(:protected_variable) do @@ -2727,6 +2763,96 @@ describe Project do it_behaves_like 'ref is protected' end + + 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 end describe '#any_lfs_file_locks?', :request_store do @@ -2977,6 +3103,16 @@ describe Project do expect(project.public_path_for_source_path('file.html', sha)).to be_nil end end + + 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 end context 'when there is no route map' do @@ -3097,11 +3233,8 @@ describe Project do let(:project) { create(:project) } it 'shows full error updating an invalid MR' do - error_message = 'Failed to replace merge_requests because one or more of the new records could not be saved.'\ - ' Validate fork Source project is not a fork of the target project' - expect { project.append_or_update_attribute(:merge_requests, [create(:merge_request)]) } - .to raise_error(ActiveRecord::RecordNotSaved, error_message) + .to raise_error(ActiveRecord::RecordInvalid, /Failed to set merge_requests:/) end it 'updates the project successfully' do @@ -3447,6 +3580,7 @@ describe Project do before do allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) + stub_application_setting(hashed_storage_enabled: false) end describe '#base_dir' do @@ -3553,10 +3687,6 @@ describe Project do let(:hashed_prefix) { File.join('@hashed', hash[0..1], hash[2..3]) } let(:hashed_path) { File.join(hashed_prefix, hash) } - before do - stub_application_setting(hashed_storage_enabled: true) - end - describe '#legacy_storage?' do it 'returns false' do expect(project.legacy_storage?).to be_falsey @@ -3787,7 +3917,7 @@ describe Project do end end - context 'when enabled on root parent', :nested_groups do + context 'when enabled on root parent' do let(:parent_group) { create(:group, parent: create(:group, :auto_devops_enabled)) } context 'when auto devops instance enabled' do @@ -3807,7 +3937,7 @@ describe Project do end end - context 'when disabled on root parent', :nested_groups do + context 'when disabled on root parent' do let(:parent_group) { create(:group, parent: create(:group, :auto_devops_disabled)) } context 'when auto devops instance enabled' do @@ -4019,7 +4149,7 @@ describe Project do context 'with a ref that is not the default branch' do it 'returns the latest successful pipeline for the given ref' do - expect(project.ci_pipelines).to receive(:latest_successful_for).with('foo') + expect(project.ci_pipelines).to receive(:latest_successful_for_ref).with('foo') project.latest_successful_pipeline_for('foo') end @@ -4047,7 +4177,7 @@ describe Project do it 'memoizes and returns the latest successful pipeline for the default branch' do pipeline = double(:pipeline) - expect(project.ci_pipelines).to receive(:latest_successful_for) + expect(project.ci_pipelines).to receive(:latest_successful_for_ref) .with(project.default_branch) .and_return(pipeline) .once @@ -4234,6 +4364,39 @@ describe Project do end end + describe '#has_active_hooks?' do + set(:project) { create(:project) } + + 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 + end + end + + describe '#has_active_services?' do + set(:project) { create(:project) } + + 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 + describe '#badges' do let(:project_group) { create(:group) } let(:project) { create(:project, path: 'avatar', namespace: project_group) } @@ -4250,18 +4413,16 @@ describe Project do expect(project.badges.count).to eq 3 end - if Group.supports_nested_objects? - context 'with nested_groups' do - let(:parent_group) { create(:group) } + context 'with nested_groups' do + let(:parent_group) { create(:group) } - before do - create_list(:group_badge, 2, group: project_group) - project_group.update(parent: parent_group) - end + before do + create_list(:group_badge, 2, group: project_group) + project_group.update(parent: parent_group) + end - it 'returns the project and the project nested groups badges' do - expect(project.badges.count).to eq 5 - end + it 'returns the project and the project nested groups badges' do + expect(project.badges.count).to eq 5 end end end @@ -4698,10 +4859,6 @@ describe Project do subject { project.object_pool_params } - before do - stub_application_setting(hashed_storage_enabled: true) - end - context 'when the objects cannot be pooled' do let(:project) { create(:project, :repository, :private) } @@ -4747,10 +4904,6 @@ describe Project do context 'when objects are poolable' do let(:project) { create(:project, :repository, :public) } - before do - stub_application_setting(hashed_storage_enabled: true) - end - it { is_expected.to be_git_objects_poolable } end end |
