diff options
Diffstat (limited to 'spec/models')
23 files changed, 578 insertions, 235 deletions
diff --git a/spec/models/analytics/devops_adoption/segment_selection_spec.rb b/spec/models/analytics/devops_adoption/segment_selection_spec.rb index 5866cbaa48e..64ef6636f2c 100644 --- a/spec/models/analytics/devops_adoption/segment_selection_spec.rb +++ b/spec/models/analytics/devops_adoption/segment_selection_spec.rb @@ -51,11 +51,13 @@ RSpec.describe Analytics::DevopsAdoption::SegmentSelection, type: :model do context 'limit the number of segment selections' do let_it_be(:segment) { create(:devops_adoption_segment) } - subject { build(:devops_adoption_segment_selection, segment: segment, project: project) } + subject { build(:devops_adoption_segment_selection, project: project, segment: segment) } before do create(:devops_adoption_segment_selection, :project, segment: segment) + segment.reload + stub_const("#{described_class}::ALLOWED_SELECTIONS_PER_SEGMENT", 1) end diff --git a/spec/models/bulk_imports/entity_spec.rb b/spec/models/bulk_imports/entity_spec.rb index ad6e3ec6f30..0732b671729 100644 --- a/spec/models/bulk_imports/entity_spec.rb +++ b/spec/models/bulk_imports/entity_spec.rb @@ -82,4 +82,68 @@ RSpec.describe BulkImports::Entity, type: :model do end end end + + describe "#update_tracker_for" do + let(:entity) { create(:bulk_import_entity) } + + it "inserts new tracker when it does not exist" do + expect do + entity.update_tracker_for(relation: :relation, has_next_page: false) + end.to change(BulkImports::Tracker, :count).by(1) + + tracker = entity.trackers.last + + expect(tracker.relation).to eq('relation') + expect(tracker.has_next_page).to eq(false) + expect(tracker.next_page).to eq(nil) + end + + it "updates the tracker if it already exist" do + create( + :bulk_import_tracker, + relation: :relation, + has_next_page: false, + entity: entity + ) + + expect do + entity.update_tracker_for(relation: :relation, has_next_page: true, next_page: 'nextPage') + end.not_to change(BulkImports::Tracker, :count) + + tracker = entity.trackers.last + + expect(tracker.relation).to eq('relation') + expect(tracker.has_next_page).to eq(true) + expect(tracker.next_page).to eq('nextPage') + end + end + + describe "#has_next_page?" do + it "queries for the given relation if it has more pages to be fetched" do + entity = create(:bulk_import_entity) + create( + :bulk_import_tracker, + relation: :relation, + has_next_page: false, + entity: entity + ) + + expect(entity.has_next_page?(:relation)).to eq(false) + end + end + + describe "#next_page_for" do + it "queries for the next page of the given relation" do + entity = create(:bulk_import_entity) + create( + :bulk_import_tracker, + relation: :relation, + has_next_page: false, + next_page: 'nextPage', + entity: entity + ) + + expect(entity.next_page_for(:relation)).to eq('nextPage') + end + end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 5ff9b4dd493..0efb014fdfc 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1151,12 +1151,26 @@ RSpec.describe Ci::Build do end context 'when transits to skipped' do - before do - build.skip! + context 'when cd_skipped_deployment_status is disabled' do + before do + stub_feature_flags(cd_skipped_deployment_status: false) + build.skip! + end + + it 'transits deployment status to canceled' do + expect(deployment).to be_canceled + end end - it 'transits deployment status to canceled' do - expect(deployment).to be_canceled + context 'when cd_skipped_deployment_status is enabled' do + before do + stub_feature_flags(cd_skipped_deployment_status: project) + build.skip! + end + + it 'transits deployment status to skipped' do + expect(deployment).to be_skipped + end end end @@ -4057,6 +4071,38 @@ RSpec.describe Ci::Build do end end + describe '#collect_codequality_reports!' do + subject(:codequality_report) { build.collect_codequality_reports!(Gitlab::Ci::Reports::CodequalityReports.new) } + + it { expect(codequality_report.degradations).to eq({}) } + + context 'when build has a codequality report' do + context 'when there is a codequality report' do + before do + create(:ci_job_artifact, :codequality, job: build, project: build.project) + end + + it 'parses blobs and add the results to the codequality report' do + expect { codequality_report }.not_to raise_error + + expect(codequality_report.degradations_count).to eq(3) + end + end + + context 'when there is an codequality report without errors' do + before do + create(:ci_job_artifact, :codequality_without_errors, job: build, project: build.project) + end + + it 'parses blobs and add the results to the codequality report' do + expect { codequality_report }.not_to raise_error + + expect(codequality_report.degradations_count).to eq(0) + end + end + end + end + describe '#collect_terraform_reports!' do let(:terraform_reports) { Gitlab::Ci::Reports::TerraformReports.new } diff --git a/spec/models/ci/build_trace_chunks/fog_spec.rb b/spec/models/ci/build_trace_chunks/fog_spec.rb index 20ca0c8b710..bc96e2584cf 100644 --- a/spec/models/ci/build_trace_chunks/fog_spec.rb +++ b/spec/models/ci/build_trace_chunks/fog_spec.rb @@ -74,6 +74,52 @@ RSpec.describe Ci::BuildTraceChunks::Fog do expect(data_store.data(model)).to eq new_data end + + context 'when S3 server side encryption is enabled' do + before do + config = Gitlab.config.artifacts.object_store.to_h + config[:storage_options] = { server_side_encryption: 'AES256' } + allow(data_store).to receive(:object_store_raw_config).and_return(config) + end + + it 'creates a file with attributes' do + expect_next_instance_of(Fog::AWS::Storage::Files) do |files| + expect(files).to receive(:create).with( + hash_including( + key: anything, + body: new_data, + 'x-amz-server-side-encryption' => 'AES256') + ).and_call_original + end + + expect(data_store.data(model)).to be_nil + + data_store.set_data(model, new_data) + + expect(data_store.data(model)).to eq new_data + end + + context 'when ci_live_trace_use_fog_attributes flag is disabled' do + before do + stub_feature_flags(ci_live_trace_use_fog_attributes: false) + end + + it 'does not pass along Fog attributes' do + expect_next_instance_of(Fog::AWS::Storage::Files) do |files| + expect(files).to receive(:create).with( + key: anything, + body: new_data + ).and_call_original + end + + expect(data_store.data(model)).to be_nil + + data_store.set_data(model, new_data) + + expect(data_store.data(model)).to eq new_data + end + end + end end end diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index 26851c93ac3..ef21ca8f100 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -96,6 +96,22 @@ RSpec.describe Ci::JobArtifact do end end + describe '.codequality_reports' do + subject { described_class.codequality_reports } + + context 'when there is a codequality report' do + let!(:artifact) { create(:ci_job_artifact, :codequality) } + + it { is_expected.to eq([artifact]) } + end + + context 'when there are no codequality reports' do + let!(:artifact) { create(:ci_job_artifact, :archive) } + + it { is_expected.to be_empty } + end + end + describe '.terraform_reports' do context 'when there is a terraform report' do it 'return the job artifact' do diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 1ca370dc950..5a29e7b5c6c 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -498,6 +498,16 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end end + context 'when pipeline has a codequality report' do + subject { described_class.with_reports(Ci::JobArtifact.codequality_reports) } + + let(:pipeline_with_report) { create(:ci_pipeline, :with_codequality_reports) } + + it 'selects the pipeline' do + is_expected.to eq([pipeline_with_report]) + end + end + context 'when pipeline has a terraform report' do it 'selects the pipeline' do pipeline_with_report = create(:ci_pipeline, :with_terraform_reports) @@ -3360,6 +3370,39 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do end end + describe '#codequality_reports' do + subject(:codequality_reports) { pipeline.codequality_reports } + + context 'when pipeline has multiple builds with codequality reports' do + let(:build_rspec) { create(:ci_build, :success, name: 'rspec', pipeline: pipeline, project: project) } + let(:build_golang) { create(:ci_build, :success, name: 'golang', pipeline: pipeline, project: project) } + + before do + create(:ci_job_artifact, :codequality, job: build_rspec, project: project) + create(:ci_job_artifact, :codequality_without_errors, job: build_golang, project: project) + end + + it 'returns codequality report with collected data' do + expect(codequality_reports.degradations_count).to eq(3) + end + + context 'when builds are retried' do + let(:build_rspec) { create(:ci_build, :retried, :success, name: 'rspec', pipeline: pipeline, project: project) } + let(:build_golang) { create(:ci_build, :retried, :success, name: 'golang', pipeline: pipeline, project: project) } + + it 'returns a codequality reports without degradations' do + expect(codequality_reports.degradations).to be_empty + end + end + end + + context 'when pipeline does not have any builds with codequality reports' do + it 'returns codequality reports without degradations' do + expect(codequality_reports.degradations).to be_empty + end + end + end + describe '#total_size' do let!(:build_job1) { create(:ci_build, pipeline: pipeline, stage_idx: 0) } let!(:build_job2) { create(:ci_build, pipeline: pipeline, stage_idx: 0) } diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb index ad1ebd4966a..5212e321a55 100644 --- a/spec/models/clusters/applications/helm_spec.rb +++ b/spec/models/clusters/applications/helm_spec.rb @@ -19,35 +19,9 @@ RSpec.describe Clusters::Applications::Helm do end describe '#can_uninstall?' do - context "with other existing applications" do - Clusters::Cluster::APPLICATIONS.keys.each do |application_name| - next if application_name == 'helm' + subject(:application) { build(:clusters_applications_helm).can_uninstall? } - it "is false when #{application_name} is installed" do - cluster_application = create("clusters_applications_#{application_name}".to_sym) - - helm = cluster_application.cluster.application_helm - - expect(helm.allowed_to_uninstall?).to be_falsy - end - end - - it 'executes a single query only' do - cluster_application = create(:clusters_applications_ingress) - helm = cluster_application.cluster.application_helm - - query_count = ActiveRecord::QueryRecorder.new { helm.allowed_to_uninstall? }.count - expect(query_count).to eq(1) - end - end - - context "without other existing applications" do - subject { helm.can_uninstall? } - - let(:helm) { create(:clusters_applications_helm) } - - it { is_expected.to be_truthy } - end + it { is_expected.to eq true } end describe '#issue_client_cert' do @@ -135,14 +109,4 @@ RSpec.describe Clusters::Applications::Helm do end end end - - describe '#post_uninstall' do - let(:helm) { create(:clusters_applications_helm, :installed) } - - it do - expect(helm.cluster.kubeclient).to receive(:delete_namespace).with('gitlab-managed-apps') - - helm.post_uninstall - end - end end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index 9afacd518af..3da1509f075 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -202,6 +202,31 @@ RSpec.describe Deployment do deployment.cancel! end end + + context 'when deployment was skipped' do + let(:deployment) { create(:deployment, :running) } + + it 'has correct status' do + deployment.skip! + + expect(deployment).to be_skipped + expect(deployment.finished_at).to be_nil + end + + it 'does not execute Deployments::LinkMergeRequestWorker asynchronously' do + expect(Deployments::LinkMergeRequestWorker) + .not_to receive(:perform_async).with(deployment.id) + + deployment.skip! + end + + it 'does not execute Deployments::ExecuteHooksWorker' do + expect(Deployments::ExecuteHooksWorker) + .not_to receive(:perform_async).with(deployment.id) + + deployment.skip! + end + end end describe '#success?' do @@ -320,6 +345,7 @@ RSpec.describe Deployment do deployment2 = create(:deployment, status: :running ) create(:deployment, status: :failed ) create(:deployment, status: :canceled ) + create(:deployment, status: :skipped) is_expected.to contain_exactly(deployment1, deployment2) end @@ -346,10 +372,54 @@ RSpec.describe Deployment do it 'retrieves deployments with deployable builds' do with_deployable = create(:deployment) create(:deployment, deployable: nil) + create(:deployment, deployable_type: 'CommitStatus', deployable_id: non_existing_record_id) is_expected.to contain_exactly(with_deployable) end end + + describe 'visible' do + subject { described_class.visible } + + it 'retrieves the visible deployments' do + deployment1 = create(:deployment, status: :running) + deployment2 = create(:deployment, status: :success) + deployment3 = create(:deployment, status: :failed) + deployment4 = create(:deployment, status: :canceled) + create(:deployment, status: :skipped) + + is_expected.to contain_exactly(deployment1, deployment2, deployment3, deployment4) + end + end + end + + describe 'latest_for_sha' do + subject { described_class.latest_for_sha(sha) } + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:commits) { project.repository.commits('master', limit: 2) } + let_it_be(:deployments) { commits.reverse.map { |commit| create(:deployment, project: project, sha: commit.id) } } + let(:sha) { commits.map(&:id) } + + it 'finds the latest deployment with sha' do + is_expected.to eq(deployments.last) + end + + context 'when sha is old' do + let(:sha) { commits.last.id } + + it 'finds the latest deployment with sha' do + is_expected.to eq(deployments.first) + end + end + + context 'when sha is nil' do + let(:sha) { nil } + + it 'returns nothing' do + is_expected.to be_nil + end + end end describe '#includes_commit?' do diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index f7ce44f7281..215c733f26b 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -38,6 +38,26 @@ RSpec.describe DiffNote do it_behaves_like 'a valid diff positionable note' do subject { build(:diff_note_on_commit, project: project, commit_id: commit_id, position: position) } end + + it "is not valid when noteable is empty" do + note = build(:diff_note_on_merge_request, project: project, noteable: nil) + + note.valid? + + expect(note.errors[:noteable]).to include("doesn't support new-style diff notes") + end + + context 'when importing' do + it "does not check if it's supported" do + note = build(:diff_note_on_merge_request, project: project, noteable: nil) + note.importing = true + note.valid? + + expect(note.errors.full_messages).not_to include( + "Noteable doesn't support new-style diff notes" + ) + end + end end describe "#position=" do diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 179f2a1b0e0..27c9e62712c 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -1394,4 +1394,31 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do it { is_expected.to be(false) } end end + + describe '#cancel_deployment_jobs!' do + subject { environment.cancel_deployment_jobs! } + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:environment, reload: true) { create(:environment, project: project) } + let!(:deployment) { create(:deployment, project: project, environment: environment, deployable: build) } + let!(:build) { create(:ci_build, :running, project: project, environment: environment) } + + it 'cancels an active deployment job' do + subject + + expect(build.reset).to be_canceled + end + + context 'when deployable does not exist' do + before do + deployment.update_column(:deployable_id, non_existing_record_id) + end + + it 'does not raise an error' do + expect { subject }.not_to raise_error + + expect(build.reset).to be_running + end + end + end end diff --git a/spec/models/exported_protected_branch_spec.rb b/spec/models/exported_protected_branch_spec.rb new file mode 100644 index 00000000000..7886a522741 --- /dev/null +++ b/spec/models/exported_protected_branch_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ExportedProtectedBranch do + describe 'Associations' do + it { is_expected.to have_many(:push_access_levels) } + end + + describe '.push_access_levels' do + it 'returns the correct push access levels' do + exported_branch = create(:exported_protected_branch, :developers_can_push) + deploy_key = create(:deploy_key) + create(:deploy_keys_project, :write_access, project: exported_branch.project, deploy_key: deploy_key ) + create(:protected_branch_push_access_level, protected_branch: exported_branch, deploy_key: deploy_key) + dev_push_access_level = exported_branch.push_access_levels.first + + expect(exported_branch.push_access_levels).to contain_exactly(dev_push_access_level) + end + end +end diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb index 383e548c324..d3566ed04c2 100644 --- a/spec/models/instance_configuration_spec.rb +++ b/spec/models/instance_configuration_spec.rb @@ -10,31 +10,35 @@ RSpec.describe InstanceConfiguration do let(:sha256) { 'SHA256:2KJDT7xf2i68mBgJ3TVsjISntg4droLbXYLfQj0VvSY' } it 'does not return anything if file does not exist' do - stub_pub_file(exist: false) + stub_pub_file(pub_file(exist: false)) expect(subject.settings[:ssh_algorithms_hashes]).to be_empty end it 'does not return anything if file is empty' do - stub_pub_file + stub_pub_file(pub_file) - allow(File).to receive(:read).and_return('') + stub_file_read(pub_file, content: '') expect(subject.settings[:ssh_algorithms_hashes]).to be_empty end it 'returns the md5 and sha256 if file valid and exists' do - stub_pub_file + stub_pub_file(pub_file) result = subject.settings[:ssh_algorithms_hashes].select { |o| o[:md5] == md5 && o[:sha256] == sha256 } expect(result.size).to eq(InstanceConfiguration::SSH_ALGORITHMS.size) end - def stub_pub_file(exist: true) + def pub_file(exist: true) path = exist ? 'spec/fixtures/ssh_host_example_key.pub' : 'spec/fixtures/ssh_host_example_key.pub.random' - allow(subject).to receive(:ssh_algorithm_file).and_return(Rails.root.join(path)) + Rails.root.join(path) + end + + def stub_pub_file(path) + allow(subject).to receive(:ssh_algorithm_file).and_return(path) end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 16ea2989eda..81f045b4db1 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -141,7 +141,6 @@ RSpec.describe Issue do describe '.with_issue_type' do let_it_be(:issue) { create(:issue, project: reusable_project) } let_it_be(:incident) { create(:incident, project: reusable_project) } - let_it_be(:test_case) { create(:quality_test_case, project: reusable_project) } it 'gives issues with the given issue type' do expect(described_class.with_issue_type('issue')) @@ -149,8 +148,8 @@ RSpec.describe Issue do end it 'gives issues with the given issue type' do - expect(described_class.with_issue_type(%w(issue incident test_case))) - .to contain_exactly(issue, incident, test_case) + expect(described_class.with_issue_type(%w(issue incident))) + .to contain_exactly(issue, incident) end end @@ -370,6 +369,20 @@ RSpec.describe Issue do expect(link_types).not_to include(nil) end + it 'returns issues including the link creation time' do + dates = authorized_issue_a.related_issues(user).map(&:issue_link_created_at) + + expect(dates).not_to be_empty + expect(dates).not_to include(nil) + end + + it 'returns issues including the link update time' do + dates = authorized_issue_a.related_issues(user).map(&:issue_link_updated_at) + + expect(dates).not_to be_empty + expect(dates).not_to include(nil) + end + describe 'when a user cannot read cross project' do it 'only returns issues within the same project' do expect(Ability).to receive(:allowed?).with(user, :read_all_resources, :global).at_least(:once).and_call_original diff --git a/spec/models/merge_request_reviewer_spec.rb b/spec/models/merge_request_reviewer_spec.rb index 55199cd96ad..76b44abca54 100644 --- a/spec/models/merge_request_reviewer_spec.rb +++ b/spec/models/merge_request_reviewer_spec.rb @@ -9,6 +9,6 @@ RSpec.describe MergeRequestReviewer do describe 'associations' do it { is_expected.to belong_to(:merge_request).class_name('MergeRequest') } - it { is_expected.to belong_to(:reviewer).class_name('User') } + it { is_expected.to belong_to(:reviewer).class_name('User').inverse_of(:merge_request_reviewers) } end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 9574c57e46c..454c3e1ba7f 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -92,6 +92,39 @@ RSpec.describe MergeRequest, factory_default: :keep do it { is_expected.not_to include(mr_without_jira_reference) } end + context 'scopes' do + let_it_be(:user1) { create(:user) } + let_it_be(:user2) { create(:user) } + + let_it_be(:merge_request1) { create(:merge_request, :unique_branches, reviewers: [user1])} + let_it_be(:merge_request2) { create(:merge_request, :unique_branches, reviewers: [user2])} + let_it_be(:merge_request3) { create(:merge_request, :unique_branches, reviewers: [])} + + describe '.review_requested' do + it 'returns MRs that has any review requests' do + expect(described_class.review_requested).to eq([merge_request1, merge_request2]) + end + end + + describe '.no_review_requested' do + it 'returns MRs that has no review requests' do + expect(described_class.no_review_requested).to eq([merge_request3]) + end + end + + describe '.review_requested_to' do + it 'returns MRs that the user has been requested to review' do + expect(described_class.review_requested_to(user1)).to eq([merge_request1]) + end + end + + describe '.no_review_requested_to' do + it 'returns MRs that the user has been requested to review' do + expect(described_class.no_review_requested_to(user1)).to eq([merge_request2, merge_request3]) + end + end + end + describe '#squash_in_progress?' do let(:repo_path) do Gitlab::GitalyClient::StorageSettings.allow_disk_access do diff --git a/spec/models/namespace_onboarding_action_spec.rb b/spec/models/namespace_onboarding_action_spec.rb new file mode 100644 index 00000000000..9dd82c05032 --- /dev/null +++ b/spec/models/namespace_onboarding_action_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe NamespaceOnboardingAction do + it { is_expected.to belong_to :namespace } +end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 85f9005052e..0130618d004 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -19,6 +19,7 @@ RSpec.describe Namespace do it { is_expected.to have_one :aggregation_schedule } it { is_expected.to have_one :namespace_settings } it { is_expected.to have_many :custom_emoji } + it { is_expected.to have_many :namespace_onboarding_actions } end describe 'validations' do diff --git a/spec/models/pages/lookup_path_spec.rb b/spec/models/pages/lookup_path_spec.rb index f8ebc237577..632313f26d3 100644 --- a/spec/models/pages/lookup_path_spec.rb +++ b/spec/models/pages/lookup_path_spec.rb @@ -9,7 +9,6 @@ RSpec.describe Pages::LookupPath do before do stub_pages_setting(access_control: true, external_https: ["1.1.1.1:443"]) - stub_artifacts_object_storage stub_pages_object_storage(::Pages::DeploymentUploader) end @@ -117,64 +116,6 @@ RSpec.describe Pages::LookupPath do include_examples 'uses disk storage' end end - - context 'when artifact_id from build job is present in pages metadata' do - let(:artifacts_archive) { create(:ci_job_artifact, :zip, :remote_store, project: project) } - - before do - project.mark_pages_as_deployed(artifacts_archive: artifacts_archive) - end - - it 'uses artifacts object storage' do - Timecop.freeze do - expect(source).to( - eq({ - type: 'zip', - path: artifacts_archive.file.url(expire_at: 1.day.from_now), - global_id: "gid://gitlab/Ci::JobArtifact/#{artifacts_archive.id}", - sha256: artifacts_archive.file_sha256, - file_size: artifacts_archive.size, - file_count: nil - }) - ) - end - end - - context 'when artifact is not uploaded to object storage' do - let(:artifacts_archive) { create(:ci_job_artifact, :zip) } - - it 'uses file protocol', :aggregate_failures do - Timecop.freeze do - expect(source).to( - eq({ - type: 'zip', - path: 'file://' + artifacts_archive.file.path, - global_id: "gid://gitlab/Ci::JobArtifact/#{artifacts_archive.id}", - sha256: artifacts_archive.file_sha256, - file_size: artifacts_archive.size, - file_count: nil - }) - ) - end - end - - context 'when pages_serve_with_zip_file_protocol feature flag is disabled' do - before do - stub_feature_flags(pages_serve_with_zip_file_protocol: false) - end - - include_examples 'uses disk storage' - end - end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(pages_serve_from_artifacts_archive: false) - end - - include_examples 'uses disk storage' - end - end end describe '#prefix' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index c8b96963d5d..b4da57ed89b 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -33,6 +33,7 @@ RSpec.describe Project, factory_default: :keep do it { is_expected.to have_many(:deploy_keys) } it { is_expected.to have_many(:hooks) } it { is_expected.to have_many(:protected_branches) } + it { is_expected.to have_many(:exported_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) } diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index d74f5faab7f..9cf22a9392d 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -481,17 +481,9 @@ RSpec.describe Snippet do end describe '#blobs' do - let(:snippet) { create(:snippet) } - - it 'returns a blob representing the snippet data' do - blob = snippet.blob - - expect(blob).to be_a(Blob) - expect(blob.path).to eq(snippet.file_name) - expect(blob.data).to eq(snippet.content) - end - context 'when repository does not exist' do + let(:snippet) { create(:snippet) } + it 'returns empty array' do expect(snippet.blobs).to be_empty end diff --git a/spec/models/system_note_metadata_spec.rb b/spec/models/system_note_metadata_spec.rb index 9a6b57afb97..144c65d2f62 100644 --- a/spec/models/system_note_metadata_spec.rb +++ b/spec/models/system_note_metadata_spec.rb @@ -13,7 +13,7 @@ RSpec.describe SystemNoteMetadata do context 'when action type is invalid' do subject do - build(:system_note_metadata, note: build(:note), action: 'invalid_type' ) + build(:system_note_metadata, note: build(:note), action: 'invalid_type') end it { is_expected.to be_invalid } @@ -21,7 +21,15 @@ RSpec.describe SystemNoteMetadata do context 'when action type is valid' do subject do - build(:system_note_metadata, note: build(:note), action: 'merge' ) + build(:system_note_metadata, note: build(:note), action: 'merge') + end + + it { is_expected.to be_valid } + end + + context 'when importing' do + subject do + build(:system_note_metadata, note: nil, action: 'merge', importing: true) end it { is_expected.to be_valid } diff --git a/spec/models/terraform/state_spec.rb b/spec/models/terraform/state_spec.rb index ca8fe4cca3b..ef91e4a5a71 100644 --- a/spec/models/terraform/state_spec.rb +++ b/spec/models/terraform/state_spec.rb @@ -3,21 +3,13 @@ require 'spec_helper' RSpec.describe Terraform::State do - subject { create(:terraform_state, :with_file) } - - let(:terraform_state_file) { fixture_file('terraform/terraform.tfstate') } - - it { is_expected.to be_a FileStoreMounter } + subject { create(:terraform_state, :with_version) } it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:locked_by_user).class_name('User') } it { is_expected.to validate_presence_of(:project_id) } - before do - stub_terraform_state_object_storage - end - describe 'scopes' do describe '.ordered_by_name' do let_it_be(:project) { create(:project) } @@ -35,64 +27,18 @@ RSpec.describe Terraform::State do end end - describe '#file' do - context 'when a file exists' do - it 'does not use the default file' do - expect(subject.file.read).to eq(terraform_state_file) - end - end - end - - describe '#file_store' do - context 'when a value is set' do - it 'returns the value' do - [ObjectStorage::Store::LOCAL, ObjectStorage::Store::REMOTE].each do |store| - expect(build(:terraform_state, file_store: store).file_store).to eq(store) - end - end - end - end - - describe '#update_file_store' do - context 'when file is stored in object storage' do - it_behaves_like 'mounted file in object store' - end - - context 'when file is stored locally' do - before do - stub_terraform_state_object_storage(enabled: false) - end - - it_behaves_like 'mounted file in local store' - end - end - describe '#latest_file' do - subject { terraform_state.latest_file } - - context 'versioning is enabled' do - let(:terraform_state) { create(:terraform_state, :with_version) } - let(:latest_version) { terraform_state.latest_version } - - it { is_expected.to eq latest_version.file } - - context 'but no version exists yet' do - let(:terraform_state) { create(:terraform_state) } + let(:terraform_state) { create(:terraform_state, :with_version) } + let(:latest_version) { terraform_state.latest_version } - it { is_expected.to be_nil } - end - end - - context 'versioning is disabled' do - let(:terraform_state) { create(:terraform_state, :with_file) } + subject { terraform_state.latest_file } - it { is_expected.to eq terraform_state.file } + it { is_expected.to eq latest_version.file } - context 'and a version exists (migration to versioned in progress)' do - let!(:migrated_version) { create(:terraform_state_version, terraform_state: terraform_state) } + context 'but no version exists yet' do + let(:terraform_state) { create(:terraform_state) } - it { is_expected.to eq terraform_state.latest_version.file } - end + it { is_expected.to be_nil } end end @@ -115,39 +61,30 @@ RSpec.describe Terraform::State do end end - context 'versioning is disabled' do - let(:terraform_state) { create(:terraform_state, :with_file) } - - it 'modifies the existing state record' do - expect { subject }.not_to change { Terraform::StateVersion.count } + context 'versioning is disabled (migration to versioned in progress)' do + let(:terraform_state) { create(:terraform_state, versioning_enabled: false) } + let!(:migrated_version) { create(:terraform_state_version, terraform_state: terraform_state, version: 0) } - expect(terraform_state.latest_file.read).to eq(data) - end - - context 'and a version exists (migration to versioned in progress)' do - let!(:migrated_version) { create(:terraform_state_version, terraform_state: terraform_state, version: 0) } + it 'creates a new version, corrects the migrated version number, and marks the state as versioned' do + expect { subject }.to change { Terraform::StateVersion.count } - it 'creates a new version, corrects the migrated version number, and marks the state as versioned' do - expect { subject }.to change { Terraform::StateVersion.count } + expect(migrated_version.reload.version).to eq(1) + expect(migrated_version.file.read).to eq(fixture_file('terraform/terraform.tfstate')) - expect(migrated_version.reload.version).to eq(1) - expect(migrated_version.file.read).to eq(terraform_state_file) + expect(terraform_state.reload.latest_version.version).to eq(version) + expect(terraform_state.latest_version.file.read).to eq(data) + expect(terraform_state).to be_versioning_enabled + end - expect(terraform_state.reload.latest_version.version).to eq(version) - expect(terraform_state.latest_version.file.read).to eq(data) - expect(terraform_state).to be_versioning_enabled + context 'the current version cannot be determined' do + before do + migrated_version.update!(file: CarrierWaveStringFile.new('invalid-json')) end - context 'the current version cannot be determined' do - before do - migrated_version.update!(file: CarrierWaveStringFile.new('invalid-json')) - end - - it 'uses version - 1 to correct the migrated version number' do - expect { subject }.to change { Terraform::StateVersion.count } + it 'uses version - 1 to correct the migrated version number' do + expect { subject }.to change { Terraform::StateVersion.count } - expect(migrated_version.reload.version).to eq(2) - end + expect(migrated_version.reload.version).to eq(2) end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 19a6a3ce3c4..87dab84fe7b 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -99,6 +99,8 @@ RSpec.describe User do it { is_expected.to have_many(:releases).dependent(:nullify) } it { is_expected.to have_many(:metrics_users_starred_dashboards).inverse_of(:user) } it { is_expected.to have_many(:reviews).inverse_of(:author) } + it { is_expected.to have_many(:merge_request_assignees).inverse_of(:assignee) } + it { is_expected.to have_many(:merge_request_reviewers).inverse_of(:reviewer) } describe "#user_detail" do it 'does not persist `user_detail` by default' do @@ -2024,9 +2026,10 @@ RSpec.describe User do end describe '.search' do - let!(:user) { create(:user, name: 'user', username: 'usern', email: 'email@gmail.com') } - let!(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@gmail.com') } - let!(:user3) { create(:user, name: 'us', username: 'se', email: 'foo@gmail.com') } + let_it_be(:user) { create(:user, name: 'user', username: 'usern', email: 'email@example.com') } + let_it_be(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@example.com') } + let_it_be(:user3) { create(:user, name: 'us', username: 'se', email: 'foo@example.com') } + let_it_be(:email) { create(:email, user: user, email: 'alias@example.com') } describe 'name matching' do it 'returns users with a matching name with exact match first' do @@ -2056,7 +2059,7 @@ RSpec.describe User do end it 'does not return users with a partially matching Email' do - expect(described_class.search(user.email[0..2])).not_to include(user, user2) + expect(described_class.search(user.email[1...-1])).to be_empty end it 'returns users with a matching Email regardless of the casing' do @@ -2064,6 +2067,36 @@ RSpec.describe User do end end + describe 'secondary email matching' do + context 'feature flag :user_search_secondary_email is enabled' do + it 'returns users with a matching secondary email' do + expect(described_class.search(email.email)).to include(email.user) + end + + it 'does not return users with a matching part of secondary email' do + expect(described_class.search(email.email[1...-1])).to be_empty + end + + it 'returns users with a matching secondary email regardless of the casing' do + expect(described_class.search(email.email.upcase)).to include(email.user) + end + end + + context 'feature flag :user_search_secondary_email is disabled' do + before do + stub_feature_flags(user_search_secondary_email: false) + end + + it 'does not return users with a matching secondary email' do + expect(described_class.search(email.email)).not_to include(email.user) + end + + it 'does not return users with a matching part of secondary email' do + expect(described_class.search(email.email[1...-1])).to be_empty + end + end + end + describe 'username matching' do it 'returns users with a matching username' do expect(described_class.search(user.username)).to eq([user, user2]) @@ -2103,65 +2136,119 @@ RSpec.describe User do end end - describe '.search_with_secondary_emails' do - delegate :search_with_secondary_emails, to: :described_class + describe '.search_without_secondary_emails' do + let_it_be(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'someone.1@example.com' ) } + let_it_be(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'another.2@example.com' ) } + let_it_be(:email) { create(:email, user: another_user, email: 'alias@example.com') } + + it 'returns users with a matching name' do + expect(described_class.search_without_secondary_emails(user.name)).to eq([user]) + end + + it 'returns users with a partially matching name' do + expect(described_class.search_without_secondary_emails(user.name[0..2])).to eq([user]) + end + + it 'returns users with a matching name regardless of the casing' do + expect(described_class.search_without_secondary_emails(user.name.upcase)).to eq([user]) + end + + it 'returns users with a matching email' do + expect(described_class.search_without_secondary_emails(user.email)).to eq([user]) + end + + it 'does not return users with a partially matching email' do + expect(described_class.search_without_secondary_emails(user.email[1...-1])).to be_empty + end + + it 'returns users with a matching email regardless of the casing' do + expect(described_class.search_without_secondary_emails(user.email.upcase)).to eq([user]) + end + + it 'returns users with a matching username' do + expect(described_class.search_without_secondary_emails(user.username)).to eq([user]) + end + + it 'returns users with a partially matching username' do + expect(described_class.search_without_secondary_emails(user.username[0..2])).to eq([user]) + end + + it 'returns users with a matching username regardless of the casing' do + expect(described_class.search_without_secondary_emails(user.username.upcase)).to eq([user]) + end + + it 'does not return users with a matching whole secondary email' do + expect(described_class.search_without_secondary_emails(email.email)).not_to include(email.user) + end + + it 'does not return users with a matching part of secondary email' do + expect(described_class.search_without_secondary_emails(email.email[1...-1])).to be_empty + end - let!(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'john.doe@example.com' ) } - let!(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'albert.smith@example.com' ) } - let!(:email) do - create(:email, user: another_user, email: 'alias@example.com') + it 'returns no matches for an empty string' do + expect(described_class.search_without_secondary_emails('')).to be_empty + end + + it 'returns no matches for nil' do + expect(described_class.search_without_secondary_emails(nil)).to be_empty end + end + + describe '.search_with_secondary_emails' do + let_it_be(:user) { create(:user, name: 'John Doe', username: 'john.doe', email: 'someone.1@example.com' ) } + let_it_be(:another_user) { create(:user, name: 'Albert Smith', username: 'albert.smith', email: 'another.2@example.com' ) } + let_it_be(:email) { create(:email, user: another_user, email: 'alias@example.com') } it 'returns users with a matching name' do - expect(search_with_secondary_emails(user.name)).to eq([user]) + expect(described_class.search_with_secondary_emails(user.name)).to eq([user]) end it 'returns users with a partially matching name' do - expect(search_with_secondary_emails(user.name[0..2])).to eq([user]) + expect(described_class.search_with_secondary_emails(user.name[0..2])).to eq([user]) end it 'returns users with a matching name regardless of the casing' do - expect(search_with_secondary_emails(user.name.upcase)).to eq([user]) + expect(described_class.search_with_secondary_emails(user.name.upcase)).to eq([user]) end it 'returns users with a matching email' do - expect(search_with_secondary_emails(user.email)).to eq([user]) + expect(described_class.search_with_secondary_emails(user.email)).to eq([user]) end it 'does not return users with a partially matching email' do - expect(search_with_secondary_emails(user.email[0..2])).not_to include([user]) + expect(described_class.search_with_secondary_emails(user.email[1...-1])).to be_empty end it 'returns users with a matching email regardless of the casing' do - expect(search_with_secondary_emails(user.email.upcase)).to eq([user]) + expect(described_class.search_with_secondary_emails(user.email.upcase)).to eq([user]) end it 'returns users with a matching username' do - expect(search_with_secondary_emails(user.username)).to eq([user]) + expect(described_class.search_with_secondary_emails(user.username)).to eq([user]) end it 'returns users with a partially matching username' do - expect(search_with_secondary_emails(user.username[0..2])).to eq([user]) + expect(described_class.search_with_secondary_emails(user.username[0..2])).to eq([user]) end it 'returns users with a matching username regardless of the casing' do - expect(search_with_secondary_emails(user.username.upcase)).to eq([user]) + expect(described_class.search_with_secondary_emails(user.username.upcase)).to eq([user]) end it 'returns users with a matching whole secondary email' do - expect(search_with_secondary_emails(email.email)).to eq([email.user]) + expect(described_class.search_with_secondary_emails(email.email)).to eq([email.user]) end it 'does not return users with a matching part of secondary email' do - expect(search_with_secondary_emails(email.email[1..4])).not_to include([email.user]) + expect(described_class.search_with_secondary_emails(email.email[1...-1])).to be_empty end it 'returns no matches for an empty string' do - expect(search_with_secondary_emails('')).to be_empty + expect(described_class.search_with_secondary_emails('')).to be_empty end it 'returns no matches for nil' do - expect(search_with_secondary_emails(nil)).to be_empty + expect(described_class.search_with_secondary_emails(nil)).to be_empty end end |