diff options
author | Robert Speicher <rspeicher@gmail.com> | 2021-01-20 13:34:23 -0600 |
---|---|---|
committer | Robert Speicher <rspeicher@gmail.com> | 2021-01-20 13:34:23 -0600 |
commit | 6438df3a1e0fb944485cebf07976160184697d72 (patch) | |
tree | 00b09bfd170e77ae9391b1a2f5a93ef6839f2597 /spec/services | |
parent | 42bcd54d971da7ef2854b896a7b34f4ef8601067 (diff) | |
download | gitlab-ce-6438df3a1e0fb944485cebf07976160184697d72.tar.gz |
Add latest changes from gitlab-org/gitlab@13-8-stable-eev13.8.0-rc42
Diffstat (limited to 'spec/services')
78 files changed, 2232 insertions, 805 deletions
diff --git a/spec/services/alert_management/alerts/todo/create_service_spec.rb b/spec/services/alert_management/alerts/todo/create_service_spec.rb index e3d9de8b4df..fa4fd8ed0b2 100644 --- a/spec/services/alert_management/alerts/todo/create_service_spec.rb +++ b/spec/services/alert_management/alerts/todo/create_service_spec.rb @@ -58,6 +58,10 @@ RSpec.describe AlertManagement::Alerts::Todo::CreateService do create(:todo, :pending, **todo_params) end + before do + stub_feature_flags(multiple_todos: false) + end + it 'does not create a todo' do expect { result }.not_to change { Todo.count } end diff --git a/spec/services/alert_management/sync_alert_service_data_service_spec.rb b/spec/services/alert_management/sync_alert_service_data_service_spec.rb deleted file mode 100644 index ecec60011db..00000000000 --- a/spec/services/alert_management/sync_alert_service_data_service_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe AlertManagement::SyncAlertServiceDataService do - let_it_be(:alerts_service) do - AlertsService.skip_callback(:save, :after, :update_http_integration) - service = create(:alerts_service, :active) - AlertsService.set_callback(:save, :after, :update_http_integration) - - service - end - - describe '#execute' do - subject(:execute) { described_class.new(alerts_service).execute } - - context 'without http integration' do - it 'creates the integration' do - expect { execute } - .to change { AlertManagement::HttpIntegration.count }.by(1) - end - - it 'returns a success' do - expect(subject.success?).to eq(true) - end - end - - context 'existing legacy http integration' do - let_it_be(:integration) { create(:alert_management_http_integration, :legacy, project: alerts_service.project) } - - it 'updates the integration' do - expect { execute } - .to change { integration.reload.encrypted_token }.to(alerts_service.data.encrypted_token) - .and change { integration.encrypted_token_iv }.to(alerts_service.data.encrypted_token_iv) - end - - it 'returns a success' do - expect(subject.success?).to eq(true) - end - end - - context 'existing other http integration' do - let_it_be(:integration) { create(:alert_management_http_integration, project: alerts_service.project) } - - it 'creates the integration' do - expect { execute } - .to change { AlertManagement::HttpIntegration.count }.by(1) - end - - it 'returns a success' do - expect(subject.success?).to eq(true) - end - end - end -end diff --git a/spec/services/ci/build_report_result_service_spec.rb b/spec/services/ci/build_report_result_service_spec.rb index 244ffbf4bbd..7c2702af086 100644 --- a/spec/services/ci/build_report_result_service_spec.rb +++ b/spec/services/ci/build_report_result_service_spec.rb @@ -6,12 +6,6 @@ RSpec.describe Ci::BuildReportResultService do describe '#execute', :clean_gitlab_redis_shared_state do subject(:build_report_result) { described_class.new.execute(build) } - around do |example| - travel_to(DateTime.parse('2020-07-01')) do - example.run - end - end - context 'when build is finished' do let(:build) { create(:ci_build, :success, :test_reports) } diff --git a/spec/services/ci/create_downstream_pipeline_service_spec.rb b/spec/services/ci/create_downstream_pipeline_service_spec.rb index 03cea4074bf..860932d4fde 100644 --- a/spec/services/ci/create_downstream_pipeline_service_spec.rb +++ b/spec/services/ci/create_downstream_pipeline_service_spec.rb @@ -371,6 +371,26 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute' do expect { service.execute(bridge) }.to change { Ci::Pipeline.count }.by(1) end end + + context 'when downstream project does not allow user-defined variables for child pipelines' do + before do + bridge.yaml_variables = [{ key: 'BRIDGE', value: '$PIPELINE_VARIABLE-var', public: true }] + + upstream_pipeline.project.update!(restrict_user_defined_variables: true) + end + + it 'creates a new pipeline allowing variables to be passed downstream' do + expect { service.execute(bridge) }.to change { Ci::Pipeline.count }.by(1) + end + + it 'passes variables downstream from the bridge' do + pipeline = service.execute(bridge) + + pipeline.variables.map(&:key).tap do |variables| + expect(variables).to include 'BRIDGE' + end + end + end end end @@ -460,6 +480,33 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute' do expect(variable.value).to eq 'my-value-var' end end + + context 'when downstream project does not allow user-defined variables for multi-project pipelines' do + before do + downstream_project.update!(restrict_user_defined_variables: true) + end + + it 'does not create a new pipeline' do + expect { service.execute(bridge) } + .not_to change { Ci::Pipeline.count } + end + + it 'ignores variables passed downstream from the bridge' do + pipeline = service.execute(bridge) + + pipeline.variables.map(&:key).tap do |variables| + expect(variables).not_to include 'BRIDGE' + end + end + + it 'sets errors', :aggregate_failures do + service.execute(bridge) + + expect(bridge.reload).to be_failed + expect(bridge.failure_reason).to eq('downstream_pipeline_creation_failed') + expect(bridge.options[:downstream_errors]).to eq(['Insufficient permissions to set pipeline variables']) + end + end end end diff --git a/spec/services/ci/create_pipeline_service/dry_run_spec.rb b/spec/services/ci/create_pipeline_service/dry_run_spec.rb index 60c56ed0f67..c21a4ef0917 100644 --- a/spec/services/ci/create_pipeline_service/dry_run_spec.rb +++ b/spec/services/ci/create_pipeline_service/dry_run_spec.rb @@ -108,7 +108,7 @@ RSpec.describe Ci::CreatePipelineService do it_behaves_like 'returns a non persisted pipeline' it 'returns a pipeline with errors', :aggregate_failures do - error_message = "test: needs 'build'" + error_message = "'test' job needs 'build' job, but it was not added to the pipeline" expect(subject.error_messages.map(&:content)).to eq([error_message]) expect(subject.errors).not_to be_empty diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index f9015752644..e1f1bdc41a1 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -91,6 +91,23 @@ RSpec.describe Ci::CreatePipelineService do .with({ source: 'push' }, 5) end + it 'tracks included template usage' do + expect_next_instance_of(Gitlab::Ci::Pipeline::Chain::TemplateUsage) do |instance| + expect(instance).to receive(:perform!) + end + + execute_service + end + + describe 'recording a conversion event' do + it 'schedules a record conversion event worker' do + expect(Experiments::RecordConversionEventWorker).to receive(:perform_async).with(:ci_syntax_templates, user.id) + expect(Experiments::RecordConversionEventWorker).to receive(:perform_async).with(:pipelines_empty_state, user.id) + + pipeline + end + end + context 'when merge requests already exist for this source branch' do let(:merge_request_1) do create(:merge_request, source_branch: 'feature', target_branch: "master", source_project: project) @@ -481,6 +498,7 @@ RSpec.describe Ci::CreatePipelineService do expect(execute_service).not_to be_persisted expect(Ci::Pipeline.count).to eq(0) + expect(Namespaces::OnboardingPipelineCreatedWorker).not_to receive(:perform_async) end shared_examples 'a failed pipeline' do @@ -1418,6 +1436,13 @@ RSpec.describe Ci::CreatePipelineService do pipeline end + it 'schedules a namespace onboarding create action worker' do + expect(Namespaces::OnboardingPipelineCreatedWorker) + .to receive(:perform_async).with(project.namespace_id) + + pipeline + end + context 'when target sha is specified' do let(:target_sha) { merge_request.target_branch_sha } @@ -1688,9 +1713,11 @@ RSpec.describe Ci::CreatePipelineService do shared_examples 'has errors' do it 'contains the expected errors' do expect(pipeline.builds).to be_empty - expect(pipeline.yaml_errors).to eq("test_a: needs 'build_a'") - expect(pipeline.error_messages.map(&:content)).to contain_exactly("test_a: needs 'build_a'") - expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'") + + error_message = "'test_a' job needs 'build_a' job, but it was not added to the pipeline" + expect(pipeline.yaml_errors).to eq(error_message) + expect(pipeline.error_messages.map(&:content)).to contain_exactly(error_message) + expect(pipeline.errors[:base]).to contain_exactly(error_message) end end @@ -2385,16 +2412,6 @@ RSpec.describe Ci::CreatePipelineService do expect(build_names).to contain_exactly('regular-job') end - context 'when FF ci_seed_block_run_before_workflow_rules is disabled' do - before do - stub_feature_flags(ci_seed_block_run_before_workflow_rules: false) - end - - it 'does not a pipeline' do - expect(pipeline).not_to be_persisted - end - end - context 'when a job requires the same variable' do let(:config) do <<-EOY @@ -2423,16 +2440,6 @@ RSpec.describe Ci::CreatePipelineService do expect(pipeline).to be_persisted expect(build_names).to contain_exactly('build', 'test1', 'test2') end - - context 'when FF ci_seed_block_run_before_workflow_rules is disabled' do - before do - stub_feature_flags(ci_seed_block_run_before_workflow_rules: false) - end - - it 'does not a pipeline' do - expect(pipeline).not_to be_persisted - end - end end end @@ -2443,16 +2450,6 @@ RSpec.describe Ci::CreatePipelineService do expect(pipeline).not_to be_persisted end - context 'when FF ci_seed_block_run_before_workflow_rules is disabled' do - before do - stub_feature_flags(ci_seed_block_run_before_workflow_rules: false) - end - - it 'does not create a pipeline' do - expect(pipeline).not_to be_persisted - end - end - context 'when a job requires the same variable' do let(:config) do <<-EOY @@ -2480,16 +2477,6 @@ RSpec.describe Ci::CreatePipelineService do it 'does not create a pipeline' do expect(pipeline).not_to be_persisted end - - context 'when FF ci_seed_block_run_before_workflow_rules is disabled' do - before do - stub_feature_flags(ci_seed_block_run_before_workflow_rules: false) - end - - it 'does not create a pipeline' do - expect(pipeline).not_to be_persisted - end - end end end end diff --git a/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb b/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb index c8d426ee657..1edcef2977b 100644 --- a/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb +++ b/spec/services/ci/destroy_expired_job_artifacts_service_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared describe '.execute' do subject { service.execute } - let_it_be(:artifact, reload: true) do + let_it_be(:artifact, refind: true) do create(:ci_job_artifact, expire_at: 1.day.ago) end @@ -30,14 +30,16 @@ RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared it 'performs the smallest number of queries for job_artifacts' do log = ActiveRecord::QueryRecorder.new { subject } - # SELECT expired ci_job_artifacts + # SELECT expired ci_job_artifacts - 3 queries from each_batch # PRELOAD projects, routes, project_statistics # BEGIN # INSERT into ci_deleted_objects # DELETE loaded ci_job_artifacts # DELETE security_findings -- for EE # COMMIT - expect(log.count).to be_within(1).of(8) + # SELECT next expired ci_job_artifacts + + expect(log.count).to be_within(1).of(11) end end @@ -162,13 +164,21 @@ RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared end context 'when timeout happens' do + let!(:second_artifact) { create(:ci_job_artifact, expire_at: 1.day.ago) } + before do - stub_const('Ci::DestroyExpiredJobArtifactsService::LOOP_TIMEOUT', 1.second) - allow_any_instance_of(described_class).to receive(:destroy_artifacts_batch) { true } + stub_const('Ci::DestroyExpiredJobArtifactsService::LOOP_TIMEOUT', 0.seconds) + stub_const('Ci::DestroyExpiredJobArtifactsService::BATCH_SIZE', 1) + + second_artifact.job.pipeline.unlocked! + end + + it 'destroys one artifact' do + expect { subject }.to change { Ci::JobArtifact.count }.by(-1) end - it 'returns false and does not continue destroying' do - is_expected.to be_falsy + it 'reports the number of destroyed artifacts' do + is_expected.to eq(1) end end @@ -182,13 +192,13 @@ RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared let!(:second_artifact) { create(:ci_job_artifact, expire_at: 1.day.ago) } - it 'raises an error and does not continue destroying' do - is_expected.to be_falsy - end - it 'destroys one artifact' do expect { subject }.to change { Ci::JobArtifact.count }.by(-1) end + + it 'reports the number of destroyed artifacts' do + is_expected.to eq(1) + end end context 'when there are no artifacts' do @@ -199,6 +209,10 @@ RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared it 'does not raise error' do expect { subject }.not_to raise_error end + + it 'reports the number of destroyed artifacts' do + is_expected.to eq(0) + end end context 'when there are artifacts more than batch sizes' do @@ -213,33 +227,9 @@ RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared it 'destroys all expired artifacts' do expect { subject }.to change { Ci::JobArtifact.count }.by(-2) end - end - - context 'when artifact is a pipeline artifact' do - context 'when artifacts are expired' do - let!(:pipeline_artifact_1) { create(:ci_pipeline_artifact, expire_at: 1.week.ago) } - let!(:pipeline_artifact_2) { create(:ci_pipeline_artifact, expire_at: 1.week.ago) } - before do - [pipeline_artifact_1, pipeline_artifact_2].each { |pipeline_artifact| pipeline_artifact.pipeline.unlocked! } - end - - it 'destroys pipeline artifacts' do - expect { subject }.to change { Ci::PipelineArtifact.count }.by(-2) - end - end - - context 'when artifacts are not expired' do - let!(:pipeline_artifact_1) { create(:ci_pipeline_artifact, expire_at: 2.days.from_now) } - let!(:pipeline_artifact_2) { create(:ci_pipeline_artifact, expire_at: 2.days.from_now) } - - before do - [pipeline_artifact_1, pipeline_artifact_2].each { |pipeline_artifact| pipeline_artifact.pipeline.unlocked! } - end - - it 'does not destroy pipeline artifacts' do - expect { subject }.not_to change { Ci::PipelineArtifact.count } - end + it 'reports the number of destroyed artifacts' do + is_expected.to eq(2) end end @@ -255,16 +245,4 @@ RSpec.describe Ci::DestroyExpiredJobArtifactsService, :clean_gitlab_redis_shared end end end - - describe '.destroy_job_artifacts_batch' do - it 'returns a falsy value without artifacts' do - expect(service.send(:destroy_job_artifacts_batch)).to be_falsy - end - end - - describe '.destroy_pipeline_artifacts_batch' do - it 'returns a falsy value without artifacts' do - expect(service.send(:destroy_pipeline_artifacts_batch)).to be_falsy - end - end end diff --git a/spec/services/ci/pipelines/create_artifact_service_spec.rb b/spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb index 4e9248d9d1a..b48ea70aa4c 100644 --- a/spec/services/ci/pipelines/create_artifact_service_spec.rb +++ b/spec/services/ci/pipeline_artifacts/coverage_report_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ::Ci::Pipelines::CreateArtifactService do +RSpec.describe ::Ci::PipelineArtifacts::CoverageReportService do describe '#execute' do subject { described_class.new.execute(pipeline) } diff --git a/spec/services/ci/pipeline_artifacts/destroy_expired_artifacts_service_spec.rb b/spec/services/ci/pipeline_artifacts/destroy_expired_artifacts_service_spec.rb new file mode 100644 index 00000000000..ac1a590face --- /dev/null +++ b/spec/services/ci/pipeline_artifacts/destroy_expired_artifacts_service_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::PipelineArtifacts::DestroyExpiredArtifactsService do + let(:service) { described_class.new } + + describe '.execute' do + subject { service.execute } + + context 'when timeout happens' do + before do + stub_const('Ci::PipelineArtifacts::DestroyExpiredArtifactsService::LOOP_TIMEOUT', 0.1.seconds) + allow(service).to receive(:destroy_artifacts_batch) { true } + end + + it 'returns 0 and does not continue destroying' do + is_expected.to eq(0) + end + end + + context 'when there are no artifacts' do + it 'does not raise error' do + expect { subject }.not_to raise_error + end + end + + context 'when the loop limit is reached' do + before do + stub_const('::Ci::PipelineArtifacts::DestroyExpiredArtifactsService::LOOP_LIMIT', 1) + stub_const('::Ci::PipelineArtifacts::DestroyExpiredArtifactsService::BATCH_SIZE', 1) + + create_list(:ci_pipeline_artifact, 2, expire_at: 1.week.ago) + end + + it 'destroys one artifact' do + expect { subject }.to change { Ci::PipelineArtifact.count }.by(-1) + end + + it 'reports the number of destroyed artifacts' do + is_expected.to eq(1) + end + end + + context 'when there are artifacts more than batch sizes' do + before do + stub_const('Ci::PipelineArtifacts::DestroyExpiredArtifactsService::BATCH_SIZE', 1) + + create_list(:ci_pipeline_artifact, 2, expire_at: 1.week.ago) + end + + it 'destroys all expired artifacts' do + expect { subject }.to change { Ci::PipelineArtifact.count }.by(-2) + end + + it 'reports the number of destroyed artifacts' do + is_expected.to eq(2) + end + end + + context 'when artifacts are not expired' do + before do + create(:ci_pipeline_artifact, expire_at: 2.days.from_now) + end + + it 'does not destroy pipeline artifacts' do + expect { subject }.not_to change { Ci::PipelineArtifact.count } + end + + it 'reports the number of destroyed artifacts' do + is_expected.to eq(0) + end + end + end + + describe '.destroy_artifacts_batch' do + it 'returns a falsy value without artifacts' do + expect(service.send(:destroy_artifacts_batch)).to be_falsy + end + end +end diff --git a/spec/services/ci/pipeline_processing/test_cases/dag_build_fails_deploy_is_delayed_and_needs_test.yml b/spec/services/ci/pipeline_processing/test_cases/dag_build_fails_deploy_is_delayed_and_needs_test.yml new file mode 100644 index 00000000000..b729efaeab2 --- /dev/null +++ b/spec/services/ci/pipeline_processing/test_cases/dag_build_fails_deploy_is_delayed_and_needs_test.yml @@ -0,0 +1,41 @@ +config: + build: + stage: build + script: exit 1 + + test: + stage: test + script: exit 0 + + deploy: + stage: deploy + script: exit 0 + when: delayed + start_in: 5 seconds + needs: [test] + +init: + expect: + pipeline: pending + stages: + build: pending + test: created + deploy: created + jobs: + build: pending + test: created + deploy: created + +transitions: + - event: drop + jobs: [build] + expect: + pipeline: failed + stages: + build: failed + test: skipped + deploy: skipped + jobs: + build: failed + test: skipped + deploy: skipped diff --git a/spec/services/ci/pipeline_processing/test_cases/dag_build_fails_deploy_is_manual_and_needs_test.yml b/spec/services/ci/pipeline_processing/test_cases/dag_build_fails_deploy_is_manual_and_needs_test.yml new file mode 100644 index 00000000000..479fc8fd72d --- /dev/null +++ b/spec/services/ci/pipeline_processing/test_cases/dag_build_fails_deploy_is_manual_and_needs_test.yml @@ -0,0 +1,40 @@ +config: + build: + stage: build + script: exit 1 + + test: + stage: test + script: exit 0 + + deploy: + stage: deploy + script: exit 0 + when: manual + needs: [test] + +init: + expect: + pipeline: pending + stages: + build: pending + test: created + deploy: created + jobs: + build: pending + test: created + deploy: created + +transitions: + - event: drop + jobs: [build] + expect: + pipeline: failed + stages: + build: failed + test: skipped + deploy: skipped + jobs: + build: failed + test: skipped + deploy: skipped diff --git a/spec/services/ci/pipeline_trigger_service_spec.rb b/spec/services/ci/pipeline_trigger_service_spec.rb index ac077e3c30e..0cc66e67b91 100644 --- a/spec/services/ci/pipeline_trigger_service_spec.rb +++ b/spec/services/ci/pipeline_trigger_service_spec.rb @@ -3,14 +3,16 @@ require 'spec_helper' RSpec.describe Ci::PipelineTriggerService do - let(:project) { create(:project, :repository) } + include AfterNextHelpers + + let_it_be(:project) { create(:project, :repository) } before do stub_ci_pipeline_to_return_yaml_file end describe '#execute' do - let(:user) { create(:user) } + let_it_be(:user) { create(:user) } let(:result) { described_class.new(project, user, params).execute } before do @@ -29,8 +31,8 @@ RSpec.describe Ci::PipelineTriggerService do end end - context 'when params have an existsed trigger token' do - context 'when params have an existsed ref' do + context 'when params have an existing trigger token' do + context 'when params have an existing ref' do let(:params) { { token: trigger.token, ref: 'master', variables: nil } } it 'triggers a pipeline' do @@ -45,9 +47,7 @@ RSpec.describe Ci::PipelineTriggerService do context 'when commit message has [ci skip]' do before do - allow_next_instance_of(Ci::Pipeline) do |instance| - allow(instance).to receive(:git_commit_message) { '[ci skip]' } - end + allow_next(Ci::Pipeline).to receive(:git_commit_message) { '[ci skip]' } end it 'ignores [ci skip] and create as general' do diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb index c9ecbad3167..00c6de7681d 100644 --- a/spec/services/ci/play_build_service_spec.rb +++ b/spec/services/ci/play_build_service_spec.rb @@ -72,6 +72,31 @@ RSpec.describe Ci::PlayBuildService, '#execute' do expect(build.reload.job_variables.map(&:key)).to contain_exactly('first', 'second') end + + context 'when user defined variables are restricted' do + before do + project.update!(restrict_user_defined_variables: true) + end + + context 'when user is maintainer' do + before do + project.add_maintainer(user) + end + + it 'assigns the variables to the build' do + service.execute(build, job_variables) + + expect(build.reload.job_variables.map(&:key)).to contain_exactly('first', 'second') + end + end + + context 'when user is developer' do + it 'raises an error' do + expect { service.execute(build, job_variables) } + .to raise_error Gitlab::Access::AccessDeniedError + end + end + end end end diff --git a/spec/services/ci/process_build_service_spec.rb b/spec/services/ci/process_build_service_spec.rb index a6e8732f5ff..6d2af81a6e8 100644 --- a/spec/services/ci/process_build_service_spec.rb +++ b/spec/services/ci/process_build_service_spec.rb @@ -124,24 +124,46 @@ RSpec.describe Ci::ProcessBuildService, '#execute' do end context 'when build is scheduled with DAG' do + using RSpec::Parameterized::TableSyntax + let(:pipeline) { create(:ci_pipeline, ref: 'master', project: project) } - let!(:build) { create(:ci_build, :created, when: :on_success, pipeline: pipeline, scheduling_type: :dag) } + let!(:build) { create(:ci_build, :created, when: build_when, pipeline: pipeline, scheduling_type: :dag) } let!(:other_build) { create(:ci_build, :created, when: :on_success, pipeline: pipeline) } let!(:build_on_other_build) { create(:ci_build_need, build: build, name: other_build.name) } - context 'when current status is success' do - let(:current_status) { 'success' } + where(:build_when, :current_status, :after_status) do + :on_success | 'success' | 'pending' + :on_success | 'skipped' | 'skipped' + :manual | 'success' | 'manual' + :manual | 'skipped' | 'skipped' + :delayed | 'success' | 'manual' + :delayed | 'skipped' | 'skipped' + end - it 'enqueues the build' do - expect { subject }.to change { build.status }.to('pending') + with_them do + it 'proceeds the build' do + expect { subject }.to change { build.status }.to(after_status) end end - context 'when current status is skipped' do - let(:current_status) { 'skipped' } + context 'when FF skip_dag_manual_and_delayed_jobs is disabled' do + before do + stub_feature_flags(skip_dag_manual_and_delayed_jobs: false) + end - it 'skips the build' do - expect { subject }.to change { build.status }.to('skipped') + where(:build_when, :current_status, :after_status) do + :on_success | 'success' | 'pending' + :on_success | 'skipped' | 'skipped' + :manual | 'success' | 'manual' + :manual | 'skipped' | 'manual' + :delayed | 'success' | 'manual' + :delayed | 'skipped' | 'manual' + end + + with_them do + it 'proceeds the build' do + expect { subject }.to change { build.status }.to(after_status) + end end end end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 81d56a0e42a..bdf60bb3fdc 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -206,6 +206,22 @@ RSpec.describe Ci::RetryBuildService do expect(subsequent_build.reload).to be_created expect(subsequent_bridge.reload).to be_created end + + it 'updates ownership for subsequent builds' do + expect { service.execute(build) }.to change { subsequent_build.reload.user }.to(user) + end + + it 'updates ownership for subsequent bridges' do + expect { service.execute(build) }.to change { subsequent_bridge.reload.user }.to(user) + end + + it 'does not cause n+1 when updaing build ownership' do + control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) { service.execute(build) }.count + + create_list(:ci_build, 2, :skipped, stage_idx: build.stage_idx + 1, pipeline: pipeline, stage: 'deploy') + + expect { service.execute(build) }.not_to exceed_all_query_limit(control_count) + end end context 'when pipeline has other builds' do diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb index 526c2f39b46..3c6a99efbf8 100644 --- a/spec/services/ci/retry_pipeline_service_spec.rb +++ b/spec/services/ci/retry_pipeline_service_spec.rb @@ -64,6 +64,18 @@ RSpec.describe Ci::RetryPipelineService, '#execute' do expect(build('spinach 1')).to be_created expect(pipeline.reload).to be_running end + + it 'changes ownership of subsequent builds' do + expect(build('rspec 2').user).not_to eq(user) + expect(build('rspec 3').user).not_to eq(user) + expect(build('spinach 1').user).not_to eq(user) + + service.execute(pipeline) + + expect(build('rspec 2').user).to eq(user) + expect(build('rspec 3').user).to eq(user) + expect(build('spinach 1').user).to eq(user) + end end context 'when there is failed build present which was run on failure' do @@ -161,6 +173,16 @@ RSpec.describe Ci::RetryPipelineService, '#execute' do expect(build('rspec 2')).to be_created expect(pipeline.reload).to be_running end + + it 'changes ownership of subsequent builds' do + expect(build('staging').user).not_to eq(user) + expect(build('rspec 2').user).not_to eq(user) + + service.execute(pipeline) + + expect(build('staging').user).to eq(user) + expect(build('rspec 2').user).to eq(user) + end end end diff --git a/spec/services/ci/test_failure_history_service_spec.rb b/spec/services/ci/test_failure_history_service_spec.rb index e858c85490d..d9c1c8dc3fa 100644 --- a/spec/services/ci/test_failure_history_service_spec.rb +++ b/spec/services/ci/test_failure_history_service_spec.rb @@ -22,19 +22,6 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do expect(Ci::TestCaseFailure.count).to eq(2) end - context 'when feature flag for test failure history is disabled' do - before do - stub_feature_flags(test_failure_history: false) - end - - it 'does not persist data' do - execute_service - - expect(Ci::TestCase.count).to eq(0) - expect(Ci::TestCaseFailure.count).to eq(0) - end - end - context 'when pipeline is not for the default branch' do before do pipeline.update_column(:ref, 'new-feature') @@ -136,14 +123,6 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do it { is_expected.to eq(true) } end - context 'when feature flag is disabled' do - before do - stub_feature_flags(test_failure_history: false) - end - - it { is_expected.to eq(false) } - end - context 'when pipeline is not equal to the project default branch' do before do pipeline.update_column(:ref, 'some-other-branch') diff --git a/spec/services/ci/update_build_state_service_spec.rb b/spec/services/ci/update_build_state_service_spec.rb index 3112e5dda1b..63190cc5d49 100644 --- a/spec/services/ci/update_build_state_service_spec.rb +++ b/spec/services/ci/update_build_state_service_spec.rb @@ -82,8 +82,9 @@ RSpec.describe Ci::UpdateBuildStateService do let(:params) do { output: { checksum: 'crc32:12345678', bytesize: 123 }, + state: 'failed', failure_reason: 'script_failure', - state: 'failed' + exit_code: 42 } end @@ -95,6 +96,15 @@ RSpec.describe Ci::UpdateBuildStateService do expect(result.status).to eq 200 end + it 'updates the allow_failure flag' do + expect(build) + .to receive(:drop_with_exit_code!) + .with('script_failure', 42) + .and_call_original + + subject.execute + end + it 'does not increment invalid trace metric' do execute_with_stubbed_metrics! @@ -115,6 +125,15 @@ RSpec.describe Ci::UpdateBuildStateService do expect(build).to be_failed end + it 'updates the allow_failure flag' do + expect(build) + .to receive(:drop_with_exit_code!) + .with('script_failure', 42) + .and_call_original + + subject.execute + end + it 'responds with 200 OK status' do result = subject.execute diff --git a/spec/services/container_expiration_policies/cleanup_service_spec.rb b/spec/services/container_expiration_policies/cleanup_service_spec.rb index 8438073ceb0..34f69d24141 100644 --- a/spec/services/container_expiration_policies/cleanup_service_spec.rb +++ b/spec/services/container_expiration_policies/cleanup_service_spec.rb @@ -17,7 +17,7 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do it 'completely clean up the repository' do expect(Projects::ContainerRepository::CleanupTagsService) - .to receive(:new).with(project, nil, cleanup_tags_service_params).and_return(cleanup_tags_service) + .to receive(:new).with(project, nil, cleanup_tags_service_params).and_return(cleanup_tags_service) expect(cleanup_tags_service).to receive(:execute).with(repository).and_return(status: :success) response = subject @@ -34,10 +34,14 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do end context 'without a successful cleanup tags service execution' do - it 'partially clean up the repository' do + let(:cleanup_tags_service_response) { { status: :error, message: 'timeout' } } + + before do expect(Projects::ContainerRepository::CleanupTagsService) - .to receive(:new).and_return(double(execute: { status: :error, message: 'timeout' })) + .to receive(:new).and_return(double(execute: cleanup_tags_service_response)) + end + it 'partially clean up the repository' do response = subject aggregate_failures "checking the response and container repositories" do @@ -49,6 +53,39 @@ RSpec.describe ContainerExpirationPolicies::CleanupService do expect(repository.expiration_policy_completed_at).to eq(nil) end end + + context 'with a truncated cleanup tags service response' do + let(:cleanup_tags_service_response) do + { + status: :error, + original_size: 1000, + before_truncate_size: 800, + after_truncate_size: 200, + before_delete_size: 100 + } + end + + it 'partially clean up the repository' do + response = subject + + aggregate_failures "checking the response and container repositories" do + expect(response.success?).to eq(true) + expect(response.payload) + .to include( + cleanup_status: :unfinished, + container_repository_id: repository.id, + cleanup_tags_service_original_size: 1000, + cleanup_tags_service_before_truncate_size: 800, + cleanup_tags_service_after_truncate_size: 200, + cleanup_tags_service_before_delete_size: 100 + ) + expect(ContainerRepository.waiting_for_cleanup.count).to eq(1) + expect(repository.reload.cleanup_unfinished?).to be_truthy + expect(repository.expiration_policy_started_at).not_to eq(nil) + expect(repository.expiration_policy_completed_at).to eq(nil) + end + end + end end context 'with no repository' do diff --git a/spec/services/draft_notes/create_service_spec.rb b/spec/services/draft_notes/create_service_spec.rb index f0291067777..9e084dbed1c 100644 --- a/spec/services/draft_notes/create_service_spec.rb +++ b/spec/services/draft_notes/create_service_spec.rb @@ -20,6 +20,23 @@ RSpec.describe DraftNotes::CreateService do expect(draft.discussion_id).to be_nil end + it 'tracks the start event when the draft is persisted' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to receive(:track_create_review_note_action) + .with(user: user) + + draft = create_draft(note: 'This is a test') + expect(draft).to be_persisted + end + + it 'does not track the start event when the draft is not persisted' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .not_to receive(:track_create_review_note_action) + + draft = create_draft(note: 'Not a reply!', resolve_discussion: true) + expect(draft).not_to be_persisted + end + it 'cannot resolve when there is nothing to resolve' do draft = create_draft(note: 'Not a reply!', resolve_discussion: true) diff --git a/spec/services/draft_notes/publish_service_spec.rb b/spec/services/draft_notes/publish_service_spec.rb index ae0c8113904..f83e91b683f 100644 --- a/spec/services/draft_notes/publish_service_spec.rb +++ b/spec/services/draft_notes/publish_service_spec.rb @@ -43,6 +43,13 @@ RSpec.describe DraftNotes::PublishService do expect(result[:status]).to eq(:success) end + it 'does not track the publish event' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .not_to receive(:track_publish_review_action) + + publish(draft: drafts.first) + end + context 'commit_id is set' do let(:commit_id) { commit.id } @@ -74,6 +81,13 @@ RSpec.describe DraftNotes::PublishService do expect { publish }.not_to change { DraftNote.count } end + it 'does not track the publish event' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .not_to receive(:track_publish_review_action) + + publish + end + it 'returns an error' do result = publish @@ -105,6 +119,14 @@ RSpec.describe DraftNotes::PublishService do publish end + it 'tracks the publish event' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to receive(:track_publish_review_action) + .with(user: user) + + publish + end + context 'commit_id is set' do let(:commit_id) { commit.id } diff --git a/spec/services/feature_flags/create_service_spec.rb b/spec/services/feature_flags/create_service_spec.rb index 2cd19000f99..e115d8098c9 100644 --- a/spec/services/feature_flags/create_service_spec.rb +++ b/spec/services/feature_flags/create_service_spec.rb @@ -34,6 +34,12 @@ RSpec.describe FeatureFlags::CreateService do it 'does not create audit log' do expect { subject }.not_to change { AuditEvent.count } end + + it 'does not sync the feature flag to Jira' do + expect(::JiraConnect::SyncFeatureFlagsWorker).not_to receive(:perform_async) + + subject + end end context 'when feature flag is saved correctly' do @@ -54,6 +60,24 @@ RSpec.describe FeatureFlags::CreateService do expect { subject }.to change { Operations::FeatureFlag.count }.by(1) end + it 'syncs the feature flag to Jira' do + expect(::JiraConnect::SyncFeatureFlagsWorker).to receive(:perform_async).with(Integer, Integer) + + subject + end + + context 'the feature flag is disabled' do + before do + stub_feature_flags(jira_sync_feature_flags: false) + end + + it 'does not sync the feature flag to Jira' do + expect(::JiraConnect::SyncFeatureFlagsWorker).not_to receive(:perform_async) + + subject + end + end + it 'creates audit event' do expected_message = 'Created feature flag <strong>feature_flag</strong> '\ 'with description <strong>"description"</strong>. '\ diff --git a/spec/services/feature_flags/update_service_spec.rb b/spec/services/feature_flags/update_service_spec.rb index 66a75a2c24e..8c4055ddd9e 100644 --- a/spec/services/feature_flags/update_service_spec.rb +++ b/spec/services/feature_flags/update_service_spec.rb @@ -26,6 +26,24 @@ RSpec.describe FeatureFlags::UpdateService do expect(subject[:status]).to eq(:success) end + context 'the feature flag is disabled' do + before do + stub_feature_flags(jira_sync_feature_flags: false) + end + + it 'does not sync the feature flag to Jira' do + expect(::JiraConnect::SyncFeatureFlagsWorker).not_to receive(:perform_async) + + subject + end + end + + it 'syncs the feature flag to Jira' do + expect(::JiraConnect::SyncFeatureFlagsWorker).to receive(:perform_async).with(Integer, Integer) + + subject + end + it 'creates audit event with correct message' do name_was = feature_flag.name @@ -52,6 +70,12 @@ RSpec.describe FeatureFlags::UpdateService do it 'does not create audit event' do expect { subject }.not_to change { AuditEvent.count } end + + it 'does not sync the feature flag to Jira' do + expect(::JiraConnect::SyncFeatureFlagsWorker).not_to receive(:perform_async) + + subject + end end context 'when user is reporter' do diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb index c7bf006dab0..cc3ba21f002 100644 --- a/spec/services/git/branch_push_service_spec.rb +++ b/spec/services/git/branch_push_service_spec.rb @@ -554,7 +554,7 @@ RSpec.describe Git::BranchPushService, services: true do end describe "housekeeping" do - let(:housekeeping) { Projects::HousekeepingService.new(project) } + let(:housekeeping) { Repositories::HousekeepingService.new(project) } before do # Flush any raw key-value data stored by the housekeeping code. @@ -562,7 +562,7 @@ RSpec.describe Git::BranchPushService, services: true do Gitlab::Redis::Queues.with { |conn| conn.flushall } Gitlab::Redis::SharedState.with { |conn| conn.flushall } - allow(Projects::HousekeepingService).to receive(:new).and_return(housekeeping) + allow(Repositories::HousekeepingService).to receive(:new).and_return(housekeeping) end after do diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb index 4f5bc3a3d5a..f0cd42c1948 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -63,6 +63,10 @@ RSpec.describe Groups::CreateService, '#execute' do end it { is_expected.to be_persisted } + + it 'adds an onboarding progress record' do + expect { subject }.to change(OnboardingProgress, :count).from(0).to(1) + end end context 'when user can not create a group' do @@ -84,6 +88,10 @@ RSpec.describe Groups::CreateService, '#execute' do end it { is_expected.to be_persisted } + + it 'does not add an onboarding progress record' do + expect { subject }.not_to change(OnboardingProgress, :count).from(0) + end end context 'as guest' do diff --git a/spec/services/groups/destroy_service_spec.rb b/spec/services/groups/destroy_service_spec.rb index e06f09d0463..2f9bb72939a 100644 --- a/spec/services/groups/destroy_service_spec.rb +++ b/spec/services/groups/destroy_service_spec.rb @@ -135,51 +135,120 @@ RSpec.describe Groups::DestroyService do end describe 'authorization updates', :sidekiq_inline do - context 'shared groups' do + context 'for solo groups' do + context 'group is deleted' do + it 'updates project authorization' do + expect { destroy_group(group, user, false) }.to( + change { user.can?(:read_project, project) }.from(true).to(false)) + end + + it 'does not make use of a specific service to update project_authorizations records' do + expect(UserProjectAccessChangedService) + .not_to receive(:new).with(group.user_ids_for_project_authorizations) + + destroy_group(group, user, false) + end + end + end + + context 'for shared groups within different hierarchies' do + let(:shared_with_group) { group } let!(:shared_group) { create(:group, :private) } let!(:shared_group_child) { create(:group, :private, parent: shared_group) } + let!(:shared_group_user) { create(:user) } let!(:project) { create(:project, group: shared_group) } let!(:project_child) { create(:project, group: shared_group_child) } before do - create(:group_group_link, shared_group: shared_group, shared_with_group: group) - group.refresh_members_authorized_projects + shared_group.add_user(shared_group_user, Gitlab::Access::OWNER) + + create(:group_group_link, shared_group: shared_group, shared_with_group: shared_with_group) + shared_with_group.refresh_members_authorized_projects + end + + context 'the shared group is deleted' do + it 'updates project authorization' do + expect(shared_group_user.can?(:read_project, project)).to eq(true) + expect(shared_group_user.can?(:read_project, project_child)).to eq(true) + + destroy_group(shared_group, shared_group_user, false) + + expect(shared_group_user.can?(:read_project, project)).to eq(false) + expect(shared_group_user.can?(:read_project, project_child)).to eq(false) + end + + it 'does not make use of specific service to update project_authorizations records' do + expect(UserProjectAccessChangedService) + .not_to receive(:new).with(shared_group.user_ids_for_project_authorizations).and_call_original + + destroy_group(shared_group, shared_group_user, false) + end end - it 'updates project authorization' do - expect(user.can?(:read_project, project)).to eq(true) - expect(user.can?(:read_project, project_child)).to eq(true) + context 'the shared_with group is deleted' do + it 'updates project authorization' do + expect(user.can?(:read_project, project)).to eq(true) + expect(user.can?(:read_project, project_child)).to eq(true) - destroy_group(group, user, false) + destroy_group(shared_with_group, user, false) - expect(user.can?(:read_project, project)).to eq(false) - expect(user.can?(:read_project, project_child)).to eq(false) + expect(user.can?(:read_project, project)).to eq(false) + expect(user.can?(:read_project, project_child)).to eq(false) + end + + it 'makes use of a specific service to update project_authorizations records' do + expect(UserProjectAccessChangedService) + .to receive(:new).with(shared_with_group.user_ids_for_project_authorizations).and_call_original + + destroy_group(shared_with_group, user, false) + end end end - context 'shared groups in the same group hierarchy' do - let!(:subgroup) { create(:group, :private, parent: group) } - let!(:subgroup_user) { create(:user) } + context 'for shared groups in the same group hierarchy' do + let(:shared_group) { group } + let(:shared_with_group) { nested_group } + let!(:shared_with_group_user) { create(:user) } before do - subgroup.add_user(subgroup_user, Gitlab::Access::MAINTAINER) + shared_with_group.add_user(shared_with_group_user, Gitlab::Access::MAINTAINER) - create(:group_group_link, shared_group: group, shared_with_group: subgroup) - subgroup.refresh_members_authorized_projects + create(:group_group_link, shared_group: shared_group, shared_with_group: shared_with_group) + shared_with_group.refresh_members_authorized_projects end - context 'group is deleted' do + context 'the shared group is deleted' do it 'updates project authorization' do - expect { destroy_group(group, user, false) }.to( - change { subgroup_user.can?(:read_project, project) }.from(true).to(false)) + expect { destroy_group(shared_group, user, false) }.to( + change { shared_with_group_user.can?(:read_project, project) }.from(true).to(false)) + end + + it 'does not make use of a specific service to update project authorizations' do + # Due to the recursive nature of `Groups::DestroyService`, `UserProjectAccessChangedService` + # will still be executed for the nested group as they fall under the same hierarchy + # and hence we need to account for this scenario. + expect(UserProjectAccessChangedService) + .to receive(:new).with(shared_with_group.user_ids_for_project_authorizations).and_call_original + + expect(UserProjectAccessChangedService) + .not_to receive(:new).with(shared_group.user_ids_for_project_authorizations) + + destroy_group(shared_group, user, false) end end - context 'subgroup is deleted' do + context 'the shared_with group is deleted' do it 'updates project authorization' do - expect { destroy_group(subgroup, user, false) }.to( - change { subgroup_user.can?(:read_project, project) }.from(true).to(false)) + expect { destroy_group(shared_with_group, user, false) }.to( + change { shared_with_group_user.can?(:read_project, project) }.from(true).to(false)) + end + + it 'makes use of a specific service to update project authorizations' do + expect(UserProjectAccessChangedService) + .to receive(:new).with(shared_with_group.user_ids_for_project_authorizations).and_call_original + + destroy_group(shared_with_group, user, false) end end end diff --git a/spec/services/incident_management/pager_duty/process_webhook_service_spec.rb b/spec/services/incident_management/pager_duty/process_webhook_service_spec.rb index 4c8aebe5fe2..0caffb16f42 100644 --- a/spec/services/incident_management/pager_duty/process_webhook_service_spec.rb +++ b/spec/services/incident_management/pager_duty/process_webhook_service_spec.rb @@ -17,7 +17,7 @@ RSpec.describe IncidentManagement::PagerDuty::ProcessWebhookService do let(:webhook_payload) { Gitlab::Json.parse(fixture_file('pager_duty/webhook_incident_trigger.json')) } let(:token) { nil } - subject(:execute) { described_class.new(project, nil, webhook_payload).execute(token) } + subject(:execute) { described_class.new(project, webhook_payload).execute(token) } context 'when PagerDuty webhook setting is active' do let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: true) } diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb index f2bc4f717af..79543fe9f5d 100644 --- a/spec/services/issuable/bulk_update_service_spec.rb +++ b/spec/services/issuable/bulk_update_service_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.describe Issuable::BulkUpdateService do - let(:user) { create(:user) } - let(:project) { create(:project, :repository, namespace: user.namespace) } + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :repository, namespace: user.namespace) } def bulk_update(issuables, extra_params = {}) bulk_update_params = extra_params @@ -31,6 +31,23 @@ RSpec.describe Issuable::BulkUpdateService do end end + shared_examples 'updates iterations' do + it 'succeeds' do + result = bulk_update(issuables, sprint_id: iteration.id) + + expect(result.success?).to be_truthy + expect(result.payload[:count]).to eq(issuables.count) + end + + it 'updates the issuables iteration' do + bulk_update(issuables, sprint_id: iteration.id) + + issuables.each do |issuable| + expect(issuable.reload.iteration).to eq(iteration) + end + end + end + shared_examples 'updating labels' do def create_issue_with_labels(labels) create(:labeled_issue, project: project, labels: labels) @@ -233,6 +250,21 @@ RSpec.describe Issuable::BulkUpdateService do it_behaves_like 'updates milestones' end + describe 'updating iterations' do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:issuables) { [create(:issue, project: project)] } + let_it_be(:iteration) { create(:iteration, group: group) } + + let(:parent) { project } + + before do + group.add_reporter(user) + end + + it_behaves_like 'updates iterations' + end + describe 'updating labels' do let(:bug) { create(:label, project: project) } let(:regression) { create(:label, project: project) } @@ -283,7 +315,7 @@ RSpec.describe Issuable::BulkUpdateService do end context 'with issuables at a group level' do - let(:group) { create(:group) } + let_it_be(:group) { create(:group) } let(:parent) { group } before do @@ -315,6 +347,19 @@ RSpec.describe Issuable::BulkUpdateService do end end + describe 'updating iterations' do + let_it_be(:iteration) { create(:iteration, group: group) } + let_it_be(:project) { create(:project, :repository, group: group) } + + context 'when issues' do + let_it_be(:issue1) { create(:issue, project: project) } + let_it_be(:issue2) { create(:issue, project: project) } + let_it_be(:issuables) { [issue1, issue2] } + + it_behaves_like 'updates iterations' + end + end + describe 'updating labels' do let(:project) { create(:project, :repository, group: group) } let(:bug) { create(:group_label, group: group) } diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index 9076fb11c9b..dc545f57d23 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -112,10 +112,14 @@ RSpec.describe Issues::CloseService do end context "closed by a merge request", :sidekiq_might_not_need_inline do - it 'mentions closure via a merge request' do + subject(:close_issue) do perform_enqueued_jobs do described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request) end + end + + it 'mentions closure via a merge request' do + close_issue email = ActionMailer::Base.deliveries.last @@ -124,12 +128,15 @@ RSpec.describe Issues::CloseService do expect(email.body.parts.map(&:body)).to all(include(closing_merge_request.to_reference)) end + it_behaves_like 'records an onboarding progress action', :issue_auto_closed do + let(:namespace) { project.namespace } + end + context 'when user cannot read merge request' do it 'does not mention merge request' do project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED) - perform_enqueued_jobs do - described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request) - end + + close_issue email = ActionMailer::Base.deliveries.last body_text = email.body.parts.map(&:body).join(" ") @@ -141,13 +148,11 @@ RSpec.describe Issues::CloseService do end context 'updating `metrics.first_mentioned_in_commit_at`' do - subject { described_class.new(project, user).close_issue(issue, closed_via: closing_merge_request) } - context 'when `metrics.first_mentioned_in_commit_at` is not set' do it 'uses the first commit authored timestamp' do expected = closing_merge_request.commits.first.authored_date - subject + close_issue expect(issue.metrics.first_mentioned_in_commit_at).to eq(expected) end @@ -159,7 +164,7 @@ RSpec.describe Issues::CloseService do end it 'does not update the metrics' do - expect { subject }.not_to change { issue.metrics.first_mentioned_in_commit_at } + expect { close_issue }.not_to change { issue.metrics.first_mentioned_in_commit_at } end end @@ -167,7 +172,7 @@ RSpec.describe Issues::CloseService do let(:closing_merge_request) { create(:merge_request, :without_diffs, source_project: project) } it 'does not update the metrics' do - subject + close_issue expect(issue.metrics.first_mentioned_in_commit_at).to be_nil end @@ -206,7 +211,7 @@ RSpec.describe Issues::CloseService do end context "valid params" do - def close_issue + subject(:close_issue) do perform_enqueued_jobs do described_class.new(project, user).close_issue(issue) end @@ -290,6 +295,8 @@ RSpec.describe Issues::CloseService do close_issue end + + it_behaves_like 'does not record an onboarding progress action' end context 'when issue is not confidential' do diff --git a/spec/services/issues/export_csv_service_spec.rb b/spec/services/issues/export_csv_service_spec.rb index 8072b7a478e..fd1bcf82ccd 100644 --- a/spec/services/issues/export_csv_service_spec.rb +++ b/spec/services/issues/export_csv_service_spec.rb @@ -20,7 +20,9 @@ RSpec.describe Issues::ExportCsvService do end it 'renders with a target filesize' do - expect(subject.csv_builder).to receive(:render).with(described_class::TARGET_FILESIZE) + expect_next_instance_of(CsvBuilder) do |csv_builder| + expect(csv_builder).to receive(:render).with(described_class::TARGET_FILESIZE).once + end subject.email(user) end diff --git a/spec/services/jira_connect/sync_service_spec.rb b/spec/services/jira_connect/sync_service_spec.rb index 4b434348146..edd0bad70f5 100644 --- a/spec/services/jira_connect/sync_service_spec.rb +++ b/spec/services/jira_connect/sync_service_spec.rb @@ -45,11 +45,11 @@ RSpec.describe JiraConnect::SyncService do it 'logs the response as an error' do expect_next(client).to store_info([ { 'errorMessages' => ['some error message'] }, - { 'rejectedBuilds' => ['x'] } + { 'errorMessages' => ['x'] } ]) expect_log(:error, { 'errorMessages' => ['some error message'] }) - expect_log(:error, { 'rejectedBuilds' => ['x'] }) + expect_log(:error, { 'errorMessages' => ['x'] }) subject end diff --git a/spec/services/jira_connect_subscriptions/create_service_spec.rb b/spec/services/jira_connect_subscriptions/create_service_spec.rb index 9750c671fa2..5f467a07a78 100644 --- a/spec/services/jira_connect_subscriptions/create_service_spec.rb +++ b/spec/services/jira_connect_subscriptions/create_service_spec.rb @@ -49,18 +49,6 @@ RSpec.describe JiraConnectSubscriptions::CreateService do subject end - - context 'when the jira_connect_full_namespace_sync feature flag is disabled' do - before do - stub_feature_flags(jira_connect_full_namespace_sync: false) - end - - specify do - expect(JiraConnect::SyncProjectWorker).not_to receive(:bulk_perform_in_with_contexts) - - subject - end - end end end diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb index e8a4a798b20..50efee9f43c 100644 --- a/spec/services/members/create_service_spec.rb +++ b/spec/services/members/create_service_spec.rb @@ -2,26 +2,41 @@ require 'spec_helper' -RSpec.describe Members::CreateService do - let_it_be(:project) { create(:project) } +RSpec.describe Members::CreateService, :clean_gitlab_redis_shared_state, :sidekiq_inline do + let_it_be(:source) { create(:project) } let_it_be(:user) { create(:user) } - let_it_be(:project_user) { create(:user) } - let_it_be(:user_ids) { project_user.id.to_s } + let_it_be(:member) { create(:user) } + let_it_be(:user_ids) { member.id.to_s } let_it_be(:access_level) { Gitlab::Access::GUEST } let(:params) { { user_ids: user_ids, access_level: access_level } } - subject(:execute_service) { described_class.new(user, params).execute(project) } + subject(:execute_service) { described_class.new(user, params).execute(source) } before do - project.add_maintainer(user) - allow(Namespaces::OnboardingUserAddedWorker).to receive(:idempotent?).and_return(false) + if source.is_a?(Project) + source.add_maintainer(user) + OnboardingProgress.onboard(source.namespace) + else + source.add_owner(user) + OnboardingProgress.onboard(source) + end end context 'when passing valid parameters' do it 'adds a user to members' do expect(execute_service[:status]).to eq(:success) - expect(project.users).to include project_user - expect(Namespaces::OnboardingUserAddedWorker.jobs.last['args'][0]).to eq(project.id) + expect(source.users).to include member + expect(OnboardingProgress.completed?(source.namespace, :user_added)).to be(true) + end + + context 'when executing on a group' do + let_it_be(:source) { create(:group) } + + it 'adds a user to members' do + expect(execute_service[:status]).to eq(:success) + expect(source.users).to include member + expect(OnboardingProgress.completed?(source, :user_added)).to be(true) + end end end @@ -31,8 +46,8 @@ RSpec.describe Members::CreateService do it 'does not add a member' do expect(execute_service[:status]).to eq(:error) expect(execute_service[:message]).to be_present - expect(project.users).not_to include project_user - expect(Namespaces::OnboardingUserAddedWorker.jobs.size).to eq(0) + expect(source.users).not_to include member + expect(OnboardingProgress.completed?(source.namespace, :user_added)).to be(false) end end @@ -42,8 +57,8 @@ RSpec.describe Members::CreateService do it 'limits the number of users to 100' do expect(execute_service[:status]).to eq(:error) expect(execute_service[:message]).to be_present - expect(project.users).not_to include project_user - expect(Namespaces::OnboardingUserAddedWorker.jobs.size).to eq(0) + expect(source.users).not_to include member + expect(OnboardingProgress.completed?(source.namespace, :user_added)).to be(false) end end @@ -52,19 +67,19 @@ RSpec.describe Members::CreateService do it 'does not add a member' do expect(execute_service[:status]).to eq(:error) - expect(execute_service[:message]).to include("#{project_user.username}: Access level is not included in the list") - expect(project.users).not_to include project_user - expect(Namespaces::OnboardingUserAddedWorker.jobs.size).to eq(0) + expect(execute_service[:message]).to include("#{member.username}: Access level is not included in the list") + expect(source.users).not_to include member + expect(OnboardingProgress.completed?(source.namespace, :user_added)).to be(false) end end context 'when passing an existing invite user id' do - let(:user_ids) { create(:project_member, :invited, project: project).invite_email } + let(:user_ids) { create(:project_member, :invited, project: source).invite_email } it 'does not add a member' do expect(execute_service[:status]).to eq(:error) expect(execute_service[:message]).to eq('Invite email has already been taken') - expect(Namespaces::OnboardingUserAddedWorker.jobs.size).to eq(0) + expect(OnboardingProgress.completed?(source.namespace, :user_added)).to be(false) end end end diff --git a/spec/services/merge_requests/after_create_service_spec.rb b/spec/services/merge_requests/after_create_service_spec.rb index 69bab3b1ea4..9ae310d8cee 100644 --- a/spec/services/merge_requests/after_create_service_spec.rb +++ b/spec/services/merge_requests/after_create_service_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe MergeRequests::AfterCreateService do + include AfterNextHelpers + let_it_be(:merge_request) { create(:merge_request) } subject(:after_create_service) do @@ -27,6 +29,14 @@ RSpec.describe MergeRequests::AfterCreateService do execute_service end + it 'calls the merge request activity counter' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to receive(:track_create_mr_action) + .with(user: merge_request.author) + + execute_service + end + it 'creates a new merge request notification' do expect(notification_service) .to receive(:new_merge_request).with(merge_request, merge_request.author) @@ -56,11 +66,15 @@ RSpec.describe MergeRequests::AfterCreateService do execute_service end - it 'records a namespace onboarding progress action' do - expect(NamespaceOnboardingAction).to receive(:create_action) - .with(merge_request.target_project.namespace, :merge_request_created).and_call_original + it 'registers an onboarding progress action' do + OnboardingProgress.onboard(merge_request.target_project.namespace) + + expect_next(OnboardingProgressService, merge_request.target_project.namespace) + .to receive(:execute).with(action: :merge_request_created).and_call_original + + execute_service - expect { execute_service }.to change(NamespaceOnboardingAction, :count).by(1) + expect(OnboardingProgress.completed?(merge_request.target_project.namespace, :merge_request_created)).to be(true) end end end diff --git a/spec/services/merge_requests/cleanup_refs_service_spec.rb b/spec/services/merge_requests/cleanup_refs_service_spec.rb index 38c0e204e54..a1822a4d5ba 100644 --- a/spec/services/merge_requests/cleanup_refs_service_spec.rb +++ b/spec/services/merge_requests/cleanup_refs_service_spec.rb @@ -91,6 +91,26 @@ RSpec.describe MergeRequests::CleanupRefsService do it_behaves_like 'service that does not clean up merge request refs' end + context 'when a git error is raised' do + context 'Gitlab::Git::Repository::GitError' do + before do + allow(merge_request.project.repository).to receive(:delete_refs).and_raise(Gitlab::Git::Repository::GitError) + end + + it_behaves_like 'service that does not clean up merge request refs' + end + + context 'Gitlab::Git::CommandError' do + before do + allow_next_instance_of(Gitlab::Git::KeepAround) do |keep_around| + expect(keep_around).to receive(:kept_around?).and_raise(Gitlab::Git::CommandError) + end + end + + it_behaves_like 'service that does not clean up merge request refs' + end + end + context 'when cleanup schedule fails to update' do before do allow(merge_request.cleanup_schedule).to receive(:update).and_return(false) diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index 67fb4eaade5..48f56b3ec68 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -18,6 +18,7 @@ RSpec.describe MergeRequests::CloseService do describe '#execute' do it_behaves_like 'cache counters invalidator' + it_behaves_like 'merge request reviewers cache counters invalidator' context 'valid params' do let(:service) { described_class.new(project, user, {}) } @@ -75,6 +76,14 @@ RSpec.describe MergeRequests::CloseService do described_class.new(project, user, {}).execute(merge_request) end + it 'calls the merge request activity counter' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to receive(:track_close_mr_action) + .with(user: user) + + described_class.new(project, user, {}).execute(merge_request) + end + it 'refreshes the number of open merge requests for a valid MR', :use_clean_rails_memory_store_caching do service = described_class.new(project, user, {}) diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb index 86e49fe601c..be8c41bc4a1 100644 --- a/spec/services/merge_requests/create_from_issue_service_spec.rb +++ b/spec/services/merge_requests/create_from_issue_service_spec.rb @@ -66,10 +66,11 @@ RSpec.describe MergeRequests::CreateFromIssueService do expect { service.execute }.to change(target_project.merge_requests, :count).by(1) end - it 'sets the merge request author to current user', :sidekiq_might_not_need_inline do + it 'sets the merge request author to current user and assigns them', :sidekiq_might_not_need_inline do result = service.execute expect(result[:merge_request].author).to eq(user) + expect(result[:merge_request].assignees).to eq([user]) end it 'sets the merge request source branch to the new issue branch', :sidekiq_might_not_need_inline do diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index d042b318d02..4f47a22b07c 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -155,6 +155,12 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do expect(Todo.where(attributes).count).to eq 1 end + + it 'invalidates counter cache for reviewers', :use_clean_rails_memory_store_caching do + expect { merge_request } + .to change { user2.review_requested_open_merge_requests_count } + .by(1) + end end context 'when head pipelines already exist for merge request source branch', :sidekiq_inline do diff --git a/spec/services/merge_requests/export_csv_service_spec.rb b/spec/services/merge_requests/export_csv_service_spec.rb index ecb17b3fe77..4ce032c396e 100644 --- a/spec/services/merge_requests/export_csv_service_spec.rb +++ b/spec/services/merge_requests/export_csv_service_spec.rb @@ -27,11 +27,11 @@ RSpec.describe MergeRequests::ExportCsvService do let_it_be(:merge_request) { create(:merge_request, assignees: create_list(:user, 2)) } it 'contains the names of assignees' do - expect(csv['Assignees']).to eq(merge_request.assignees.map(&:name).join(', ')) + expect(csv['Assignees'].split(', ')).to match_array(merge_request.assignees.map(&:name)) end it 'contains the usernames of assignees' do - expect(csv['Assignee Usernames']).to eq(merge_request.assignees.map(&:username).join(', ')) + expect(csv['Assignee Usernames'].split(', ')).to match_array(merge_request.assignees.map(&:username)) end end diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index d0e3102f157..dd37d87e3f5 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -37,6 +37,10 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request.in_progress_merge_commit_sha).to be_nil end + it 'does not update squash_commit_sha if it is not a squash' do + expect(merge_request.squash_commit_sha).to be_nil + end + it 'sends email to user2 about merge of new merge_request' do email = ActionMailer::Base.deliveries.last expect(email.to.first).to eq(user2.email) @@ -76,6 +80,12 @@ RSpec.describe MergeRequests::MergeService do expect(merge_commit.message).to eq('Merge commit message') expect(squash_commit.message).to eq("Squash commit message\n") end + + it 'persists squash_commit_sha' do + squash_commit = merge_request.merge_commit.parents.last + + expect(merge_request.squash_commit_sha).to eq(squash_commit.id) + end end end @@ -361,6 +371,7 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request).to be_open expect(merge_request.merge_commit_sha).to be_nil + expect(merge_request.squash_commit_sha).to be_nil expect(merge_request.merge_error).to include(error_message) expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) end @@ -381,6 +392,7 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request).to be_open expect(merge_request.merge_commit_sha).to be_nil + expect(merge_request.squash_commit_sha).to be_nil expect(merge_request.merge_error).to include(error_message) expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) end @@ -395,10 +407,27 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request).to be_open expect(merge_request.merge_commit_sha).to be_nil + expect(merge_request.squash_commit_sha).to be_nil expect(merge_request.merge_error).to include(error_message) expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) end + it 'logs and saves error if there is an PreReceiveError exception' do + error_message = 'error message' + + allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "GitLab: #{error_message}") + allow(service).to receive(:execute_hooks) + merge_request.update!(squash: true) + + service.execute(merge_request) + + expect(merge_request).to be_open + expect(merge_request.merge_commit_sha).to be_nil + expect(merge_request.squash_commit_sha).to be_nil + expect(merge_request.merge_error).to include('Something went wrong during merge pre-receive hook') + expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) + end + context 'when fast-forward merge is not allowed' do before do allow_any_instance_of(Repository).to receive(:ancestor?).and_return(nil) @@ -415,6 +444,7 @@ RSpec.describe MergeRequests::MergeService do expect(merge_request).to be_open expect(merge_request.merge_commit_sha).to be_nil + expect(merge_request.squash_commit_sha).to be_nil expect(merge_request.merge_error).to include(error_message) expect(Gitlab::AppLogger).to have_received(:error).with(a_string_matching(error_message)) end diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb index 725fc16fa7c..17bfa9d7368 100644 --- a/spec/services/merge_requests/mergeability_check_service_spec.rb +++ b/spec/services/merge_requests/mergeability_check_service_spec.rb @@ -124,14 +124,6 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar it_behaves_like 'mergeable merge request' - context 'when lock is disabled' do - before do - stub_feature_flags(merge_ref_auto_sync_lock: false) - end - - it_behaves_like 'mergeable merge request' - end - context 'when concurrent calls' do it 'waits first lock and returns "cached" result in subsequent calls' do threads = execute_within_threads(amount: 3) @@ -167,25 +159,6 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar end end - context 'disabled merge ref sync feature flag' do - before do - stub_feature_flags(merge_ref_auto_sync: false) - end - - it 'returns error and no payload' do - result = subject - - expect(result).to be_a(ServiceResponse) - expect(result.error?).to be(true) - expect(result.message).to eq('Merge ref is outdated due to disabled feature') - expect(result.payload).to be_empty - end - - it 'ignores merge-ref and updates merge status' do - expect { subject }.to change(merge_request, :merge_status).from('unchecked').to('can_be_merged') - end - end - context 'when broken' do before do expect(merge_request).to receive(:broken?) { true } @@ -305,28 +278,6 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar context 'recheck enforced' do subject { described_class.new(merge_request).execute(recheck: true) } - context 'when MR is mergeable and merge-ref auto-sync is disabled' do - before do - stub_feature_flags(merge_ref_auto_sync: false) - merge_request.mark_as_mergeable! - end - - it 'returns ServiceResponse.error' do - result = subject - - expect(result).to be_a(ServiceResponse) - expect(result.error?).to be(true) - expect(result.message).to eq('Merge ref is outdated due to disabled feature') - expect(result.payload).to be_empty - end - - it 'merge status is not changed' do - subject - - expect(merge_request.merge_status).to eq('can_be_merged') - end - end - context 'when MR is marked as mergeable, but repo is not mergeable and MR is not opened' do before do # Making sure that we don't touch the merge-status after diff --git a/spec/services/merge_requests/post_merge_service_spec.rb b/spec/services/merge_requests/post_merge_service_spec.rb index 402f753c0af..6523b5a158c 100644 --- a/spec/services/merge_requests/post_merge_service_spec.rb +++ b/spec/services/merge_requests/post_merge_service_spec.rb @@ -15,6 +15,7 @@ RSpec.describe MergeRequests::PostMergeService do describe '#execute' do it_behaves_like 'cache counters invalidator' + it_behaves_like 'merge request reviewers cache counters invalidator' it 'refreshes the number of open merge requests for a valid MR', :use_clean_rails_memory_store_caching do # Cache the counter before the MR changed state. @@ -37,6 +38,14 @@ RSpec.describe MergeRequests::PostMergeService do subject end + it 'calls the merge request activity counter' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to receive(:track_merge_mr_action) + .with(user: user) + + subject + end + it 'deletes non-latest diffs' do diff_removal_service = instance_double(MergeRequests::DeleteNonLatestDiffsService, execute: nil) diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb index 2bd83dc36a8..8541d597581 100644 --- a/spec/services/merge_requests/reopen_service_spec.rb +++ b/spec/services/merge_requests/reopen_service_spec.rb @@ -17,6 +17,7 @@ RSpec.describe MergeRequests::ReopenService do describe '#execute' do it_behaves_like 'cache counters invalidator' + it_behaves_like 'merge request reviewers cache counters invalidator' context 'valid params' do let(:service) { described_class.new(project, user, {}) } @@ -80,6 +81,14 @@ RSpec.describe MergeRequests::ReopenService do described_class.new(project, user, {}).execute(merge_request) end + it 'calls the merge request activity counter' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) + .to receive(:track_reopen_mr_action) + .with(user: user) + + described_class.new(project, user, {}).execute(merge_request) + end + it 'refreshes the number of open merge requests for a valid MR' do service = described_class.new(project, user, {}) diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index ed8872b71f7..9eb82dcd0ad 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -431,14 +431,6 @@ RSpec.describe MergeRequests::UpdateService, :mailer do describe 'merge' do it_behaves_like 'correct merge behavior' - - context 'when merge_orchestration_service feature flag is disabled' do - before do - stub_feature_flags(merge_orchestration_service: false) - end - - it_behaves_like 'correct merge behavior' - end end context 'todos' do @@ -529,6 +521,19 @@ RSpec.describe MergeRequests::UpdateService, :mailer do should_email(user2) should_email(user3) end + + it 'updates open merge request counter for reviewers', :use_clean_rails_memory_store_caching do + merge_request.reviewers = [user3] + + # Cache them to ensure the cache gets invalidated on update + expect(user2.review_requested_open_merge_requests_count).to eq(0) + expect(user3.review_requested_open_merge_requests_count).to eq(1) + + update_merge_request(reviewer_ids: [user2.id]) + + expect(user2.review_requested_open_merge_requests_count).to eq(1) + expect(user3.review_requested_open_merge_requests_count).to eq(0) + end end context 'when the milestone is removed' do @@ -837,20 +842,20 @@ RSpec.describe MergeRequests::UpdateService, :mailer do it 'does not allow a maintainer of the target project to set `allow_collaboration`' do target_project.add_developer(user) - update_merge_request(allow_collaboration: true, title: 'Updated title') + update_merge_request(allow_collaboration: false, title: 'Updated title') expect(merge_request.title).to eq('Updated title') - expect(merge_request.allow_collaboration).to be_falsy + expect(merge_request.allow_collaboration).to be_truthy end it 'is allowed by a user that can push to the source and can update the merge request' do merge_request.update!(assignees: [user]) source_project.add_developer(user) - update_merge_request(allow_collaboration: true, title: 'Updated title') + update_merge_request(allow_collaboration: false, title: 'Updated title') expect(merge_request.title).to eq('Updated title') - expect(merge_request.allow_collaboration).to be_truthy + expect(merge_request.allow_collaboration).to be_falsy end end diff --git a/spec/services/namespaces/package_settings/update_service_spec.rb b/spec/services/namespaces/package_settings/update_service_spec.rb new file mode 100644 index 00000000000..fa0c58e4c9b --- /dev/null +++ b/spec/services/namespaces/package_settings/update_service_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Namespaces::PackageSettings::UpdateService do + using RSpec::Parameterized::TableSyntax + + let_it_be_with_reload(:namespace) { create(:group) } + let_it_be(:user) { create(:user) } + let_it_be(:params) { {} } + + describe '#execute' do + subject { described_class.new(container: namespace, current_user: user, params: params).execute } + + shared_examples 'returning a success' do + it 'returns a success' do + result = subject + + expect(result.payload[:package_settings]).to be_present + expect(result.success?).to be_truthy + end + end + + shared_examples 'returning an error' do |message, http_status| + it 'returns an error' do + result = subject + + expect(result.message).to eq(message) + expect(result.status).to eq(:error) + expect(result.http_status).to eq(http_status) + end + end + + shared_examples 'updating the namespace package setting' do + it_behaves_like 'updating the namespace package setting attributes', from: { maven_duplicates_allowed: true, maven_duplicate_exception_regex: 'SNAPSHOT' }, to: { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE' } + + it_behaves_like 'returning a success' + + context 'with invalid params' do + let_it_be(:params) { { maven_duplicates_allowed: nil } } + + it_behaves_like 'not creating the namespace package setting' + + it "doesn't update the maven_duplicates_allowed" do + expect { subject } + .not_to change { package_settings.reload.maven_duplicates_allowed } + end + + it_behaves_like 'returning an error', 'Maven duplicates allowed is not included in the list', 400 + end + end + + shared_examples 'denying access to namespace package setting' do + context 'with existing namespace package setting' do + it_behaves_like 'not creating the namespace package setting' + + it_behaves_like 'returning an error', 'Access Denied', 403 + end + end + + context 'with existing namespace package setting' do + let_it_be(:package_settings) { create(:namespace_package_setting, namespace: namespace) } + let_it_be(:params) { { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'RELEASE' } } + + where(:user_role, :shared_examples_name) do + :maintainer | 'updating the namespace package setting' + :developer | 'updating the namespace package setting' + :reporter | 'denying access to namespace package setting' + :guest | 'denying access to namespace package setting' + :anonymous | 'denying access to namespace package setting' + end + + with_them do + before do + namespace.send("add_#{user_role}", user) unless user_role == :anonymous + end + + it_behaves_like params[:shared_examples_name] + end + end + + context 'without existing namespace package setting' do + let_it_be(:package_settings) { namespace.package_settings } + + where(:user_role, :shared_examples_name) do + :maintainer | 'creating the namespace package setting' + :developer | 'creating the namespace package setting' + :reporter | 'denying access to namespace package setting' + :guest | 'denying access to namespace package setting' + :anonymous | 'denying access to namespace package setting' + end + + with_them do + before do + namespace.send("add_#{user_role}", user) unless user_role == :anonymous + end + + it_behaves_like params[:shared_examples_name] + end + end + end +end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 3118956951e..20f06619e02 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -78,6 +78,12 @@ RSpec.describe Notes::CreateService do end.to change { counter.unique_events(event_names: event, start_date: 1.day.ago, end_date: 1.day.from_now) }.by(1) end + it 'does not track merge request usage data' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter).not_to receive(:track_create_comment_action) + + described_class.new(project, user, opts).execute + end + context 'in a merge request' do let_it_be(:project_with_repo) { create(:project, :repository) } let_it_be(:merge_request) do @@ -85,18 +91,6 @@ RSpec.describe Notes::CreateService do target_project: project_with_repo) end - context 'issue comment usage data' do - let(:opts) do - { note: 'Awesome comment', noteable_type: 'MergeRequest', noteable_id: merge_request.id } - end - - it 'does not track' do - expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).not_to receive(:track_issue_comment_added_action) - - described_class.new(project, user, opts).execute - end - end - context 'noteable highlight cache clearing' do let(:position) do Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb", @@ -119,6 +113,18 @@ RSpec.describe Notes::CreateService do .to receive(:unfolded_diff?) { true } end + it 'does not track issue comment usage data' do + expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).not_to receive(:track_issue_comment_added_action) + + described_class.new(project_with_repo, user, new_opts).execute + end + + it 'tracks merge request usage data' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter).to receive(:track_create_comment_action).with(note: kind_of(Note)) + + described_class.new(project_with_repo, user, new_opts).execute + end + it 'clears noteable diff cache when it was unfolded for the note position' do expect_any_instance_of(Gitlab::Diff::HighlightCache).to receive(:clear) diff --git a/spec/services/notes/destroy_service_spec.rb b/spec/services/notes/destroy_service_spec.rb index 0859c28cbe7..eebbdcc33b8 100644 --- a/spec/services/notes/destroy_service_spec.rb +++ b/spec/services/notes/destroy_service_spec.rb @@ -35,6 +35,14 @@ RSpec.describe Notes::DestroyService do end.to change { counter.unique_events(event_names: event, start_date: 1.day.ago, end_date: 1.day.from_now) }.by(1) end + it 'tracks merge request usage data' do + mr = create(:merge_request, source_project: project) + note = create(:note, project: project, noteable: mr) + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter).to receive(:track_remove_comment_action).with(note: note) + + described_class.new(project, user).execute(note) + end + context 'in a merge request' do let_it_be(:repo_project) { create(:project, :repository) } let_it_be(:merge_request) do diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb index 64aa845841b..c098500b78a 100644 --- a/spec/services/notes/quick_actions_service_spec.rb +++ b/spec/services/notes/quick_actions_service_spec.rb @@ -153,8 +153,8 @@ RSpec.describe Notes::QuickActionsService do expect(execute(note)).to be_empty end - it 'does not assign the milestone' do - expect { execute(note) }.not_to change { issue.reload.milestone } + it 'assigns the milestone' do + expect { execute(note) }.to change { issue.reload.milestone }.from(nil).to(milestone) end end @@ -195,8 +195,8 @@ RSpec.describe Notes::QuickActionsService do expect(execute(note)).to be_empty end - it 'does not remove the milestone' do - expect { execute(note) }.not_to change { issue.reload.milestone } + it 'removes the milestone' do + expect { execute(note) }.to change { issue.reload.milestone }.from(milestone).to(nil) end end diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb index e2f51c9af67..902fd9958f8 100644 --- a/spec/services/notes/update_service_spec.rb +++ b/spec/services/notes/update_service_spec.rb @@ -49,11 +49,12 @@ RSpec.describe Notes::UpdateService do it 'does not track usage data when params is blank' do expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter).not_to receive(:track_issue_comment_edited_action) + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter).not_to receive(:track_edit_comment_action) update_note({}) end - it 'tracks usage data', :clean_gitlab_redis_shared_state do + it 'tracks issue usage data', :clean_gitlab_redis_shared_state do event = Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_COMMENT_EDITED counter = Gitlab::UsageDataCounters::HLLRedisCounter @@ -63,6 +64,17 @@ RSpec.describe Notes::UpdateService do end.to change { counter.unique_events(event_names: event, start_date: 1.day.ago, end_date: 1.day.from_now) }.by(1) end + context 'when the notable is a merge request' do + let(:merge_request) { create(:merge_request, source_project: project) } + let(:note) { create(:note, project: project, noteable: merge_request, author: user, note: "Old note #{user2.to_reference}") } + + it 'tracks merge request usage data' do + expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter).to receive(:track_edit_comment_action).with(note: note) + + update_note(note: 'new text') + end + end + context 'with system note' do before do note.update_column(:system, true) diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 9431c023850..85234077b1f 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -2455,6 +2455,18 @@ RSpec.describe NotificationService, :mailer do let(:notification_trigger) { group.add_guest(added_user) } end end + + describe '#updated_group_member_expiration' do + let_it_be(:group_member) { create(:group_member) } + + it 'emails the user that their group membership expiry has changed' do + expect_next_instance_of(NotificationService) do |notification| + allow(notification).to receive(:updated_group_member_expiration).with(group_member) + end + + group_member.update!(expires_at: 5.days.from_now) + end + end end describe 'ProjectMember', :deliver_mails_inline do diff --git a/spec/services/onboarding_progress_service_spec.rb b/spec/services/onboarding_progress_service_spec.rb index 59b6083d38a..340face4ae8 100644 --- a/spec/services/onboarding_progress_service_spec.rb +++ b/spec/services/onboarding_progress_service_spec.rb @@ -5,28 +5,44 @@ require 'spec_helper' RSpec.describe OnboardingProgressService do describe '#execute' do let(:namespace) { create(:namespace, parent: root_namespace) } + let(:root_namespace) { nil } + let(:action) { :namespace_action } subject(:execute_service) { described_class.new(namespace).execute(action: :subscription_created) } context 'when the namespace is a root' do - let(:root_namespace) { nil } + before do + OnboardingProgress.onboard(namespace) + end - it 'records a namespace onboarding progress action for the given namespace' do - expect(NamespaceOnboardingAction).to receive(:create_action) - .with(namespace, :subscription_created).and_call_original + it 'registers a namespace onboarding progress action for the given namespace' do + execute_service - expect { execute_service }.to change(NamespaceOnboardingAction, :count).by(1) + expect(OnboardingProgress.completed?(namespace, :subscription_created)).to eq(true) end end context 'when the namespace is not the root' do - let_it_be(:root_namespace) { build(:namespace) } + let(:root_namespace) { build(:namespace) } + + before do + OnboardingProgress.onboard(root_namespace) + end + + it 'registers a namespace onboarding progress action for the root namespace' do + execute_service + + expect(OnboardingProgress.completed?(root_namespace, :subscription_created)).to eq(true) + end + end + + context 'when no namespace is passed' do + let(:namespace) { nil } - it 'records a namespace onboarding progress action for the root namespace' do - expect(NamespaceOnboardingAction).to receive(:create_action) - .with(root_namespace, :subscription_created).and_call_original + it 'does not register a namespace onboarding progress action' do + execute_service - expect { execute_service }.to change(NamespaceOnboardingAction, :count).by(1) + expect(OnboardingProgress.completed?(root_namespace, :subscription_created)).to be(nil) end end end diff --git a/spec/services/packages/create_event_service_spec.rb b/spec/services/packages/create_event_service_spec.rb index f581d704087..f7bab0e5a9f 100644 --- a/spec/services/packages/create_event_service_spec.rb +++ b/spec/services/packages/create_event_service_spec.rb @@ -56,7 +56,7 @@ RSpec.describe Packages::CreateEventService do end end - shared_examples 'redis package event creation' do |originator_type, expected_scope| + shared_examples 'redis package unique event creation' do |originator_type, expected_scope| context 'with feature flag disable' do before do stub_feature_flags(collect_package_events_redis: false) @@ -70,29 +70,27 @@ RSpec.describe Packages::CreateEventService do end it 'tracks the event' do - expect(::Gitlab::UsageDataCounters::GuestPackageEventCounter).not_to receive(:count) - expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(user.id, Packages::Event.allowed_event_name(expected_scope, event_name, originator_type)) + expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(/package/, values: user.id) subject end end - shared_examples 'redis package guest event creation' do |originator_type, expected_scope| + shared_examples 'redis package count event creation' do |originator_type, expected_scope| context 'with feature flag disabled' do before do stub_feature_flags(collect_package_events_redis: false) end it 'does not track the event' do - expect(::Gitlab::UsageDataCounters::GuestPackageEventCounter).not_to receive(:count) + expect(::Gitlab::UsageDataCounters::PackageEventCounter).not_to receive(:count) subject end end it 'tracks the event' do - expect(::Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(:track_event) - expect(::Gitlab::UsageDataCounters::GuestPackageEventCounter).to receive(:count).with(Packages::Event.allowed_event_name(expected_scope, event_name, originator_type)) + expect(::Gitlab::UsageDataCounters::PackageEventCounter).to receive(:count).at_least(:once) subject end @@ -102,21 +100,23 @@ RSpec.describe Packages::CreateEventService do let(:user) { create(:user) } it_behaves_like 'db package event creation', 'user', 'container' - it_behaves_like 'redis package event creation', 'user', 'container' + it_behaves_like 'redis package unique event creation', 'user', 'container' + it_behaves_like 'redis package count event creation', 'user', 'container' end context 'with a deploy token' do let(:user) { create(:deploy_token) } it_behaves_like 'db package event creation', 'deploy_token', 'container' - it_behaves_like 'redis package event creation', 'deploy_token', 'container' + it_behaves_like 'redis package unique event creation', 'deploy_token', 'container' + it_behaves_like 'redis package count event creation', 'deploy_token', 'container' end context 'with no user' do let(:user) { nil } it_behaves_like 'db package event creation', 'guest', 'container' - it_behaves_like 'redis package guest event creation', 'guest', 'container' + it_behaves_like 'redis package count event creation', 'guest', 'container' end context 'with a package as scope' do @@ -126,14 +126,15 @@ RSpec.describe Packages::CreateEventService do let(:user) { nil } it_behaves_like 'db package event creation', 'guest', 'npm' - it_behaves_like 'redis package guest event creation', 'guest', 'npm' + it_behaves_like 'redis package count event creation', 'guest', 'npm' end context 'with user' do let(:user) { create(:user) } it_behaves_like 'db package event creation', 'user', 'npm' - it_behaves_like 'redis package event creation', 'user', 'npm' + it_behaves_like 'redis package unique event creation', 'user', 'npm' + it_behaves_like 'redis package count event creation', 'user', 'npm' end end end diff --git a/spec/services/packages/debian/create_package_file_service_spec.rb b/spec/services/packages/debian/create_package_file_service_spec.rb new file mode 100644 index 00000000000..74b97a4f941 --- /dev/null +++ b/spec/services/packages/debian/create_package_file_service_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::Debian::CreatePackageFileService do + include WorkhorseHelpers + + let_it_be(:package) { create(:debian_incoming, without_package_files: true) } + + describe '#execute' do + let(:file_name) { 'libsample0_1.2.3~alpha2_amd64.deb' } + let(:fixture_path) { "spec/fixtures/packages/debian/#{file_name}" } + + let(:params) do + { + file: file, + file_name: file_name, + file_sha1: '54321', + file_md5: '12345' + }.with_indifferent_access + end + + let(:service) { described_class.new(package, params) } + + subject(:package_file) { service.execute } + + shared_examples 'a valid deb' do + it 'creates a new package file', :aggregate_failures do + expect(package_file).to be_valid + expect(package_file.file.read).to start_with('!<arch>') + expect(package_file.size).to eq(1124) + expect(package_file.file_name).to eq(file_name) + expect(package_file.file_sha1).to eq('54321') + expect(package_file.file_sha256).to eq('543212345') + expect(package_file.file_md5).to eq('12345') + expect(package_file.debian_file_metadatum).to be_valid + expect(package_file.debian_file_metadatum.file_type).to eq('unknown') + expect(package_file.debian_file_metadatum.architecture).to be_nil + expect(package_file.debian_file_metadatum.fields).to be_nil + end + end + + context 'with temp file' do + let!(:file) do + upload_path = ::Packages::PackageFileUploader.workhorse_local_upload_path + file_path = upload_path + '/' + file_name + + FileUtils.mkdir_p(upload_path) + File.write(file_path, File.read(fixture_path)) + + UploadedFile.new(file_path, filename: File.basename(file_path), sha256: '543212345') + end + + it_behaves_like 'a valid deb' + end + + context 'with remote file' do + let!(:fog_connection) do + stub_package_file_object_storage(direct_upload: true) + end + + before do + allow_next_instance_of(UploadedFile) do |uploaded_file| + allow(uploaded_file).to receive(:sha256).and_return('543212345') + end + end + + let(:tmp_object) do + fog_connection.directories.new(key: 'packages').files.create( # rubocop:disable Rails/SaveBang + key: "tmp/uploads/#{file_name}", + body: File.read(fixture_path) + ) + end + + let!(:file) { fog_to_uploaded_file(tmp_object) } + + it_behaves_like 'a valid deb' + end + + context 'package is missing' do + let(:package) { nil } + let(:params) { {} } + + it 'raises an error' do + expect { subject.execute }.to raise_error(ArgumentError, 'Invalid package') + end + end + + context 'params is empty' do + let(:params) { {} } + + it 'raises an error' do + expect { subject.execute }.to raise_error(ActiveRecord::RecordInvalid) + end + end + + context 'file is missing' do + let(:file_name) { 'libsample0_1.2.3~alpha2_amd64.deb' } + let(:file) { nil } + + it 'raises an error' do + expect { subject.execute }.to raise_error(ActiveRecord::RecordInvalid) + end + end + end +end diff --git a/spec/services/packages/debian/extract_metadata_service_spec.rb b/spec/services/packages/debian/extract_metadata_service_spec.rb new file mode 100644 index 00000000000..0aa9a67b263 --- /dev/null +++ b/spec/services/packages/debian/extract_metadata_service_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe Packages::Debian::ExtractMetadataService do + let(:service) { described_class.new(package_file) } + + subject { service.execute } + + RSpec.shared_context 'Debian ExtractMetadata Service' do |trait| + let(:package_file) { create(:debian_package_file, trait) } + end + + RSpec.shared_examples 'Test Debian ExtractMetadata Service' do |expected_file_type, expected_architecture, expected_fields| + it "returns file_type #{expected_file_type.inspect}" do + expect(subject[:file_type]).to eq(expected_file_type) + end + + it "returns architecture #{expected_architecture.inspect}" do + expect(subject[:architecture]).to eq(expected_architecture) + end + + it "returns fields #{expected_fields.nil? ? '' : 'including '}#{expected_fields.inspect}" do + if expected_fields.nil? + expect(subject[:fields]).to be_nil + else + expect(subject[:fields]).to include(**expected_fields) + end + end + end + + using RSpec::Parameterized::TableSyntax + + where(:case_name, :trait, :expected_file_type, :expected_architecture, :expected_fields) do + 'with invalid' | :invalid | :unknown | nil | nil + 'with source' | :source | :source | nil | nil + 'with dsc' | :dsc | :dsc | nil | { 'Binary': 'sample-dev, libsample0, sample-udeb' } + 'with deb' | :deb | :deb | 'amd64' | { 'Multi-Arch': 'same' } + 'with udeb' | :udeb | :udeb | 'amd64' | { 'Package': 'sample-udeb' } + 'with buildinfo' | :buildinfo | :buildinfo | nil | { 'Architecture': 'amd64 source', 'Build-Architecture': 'amd64' } + 'with changes' | :changes | :changes | nil | { 'Architecture': 'source amd64', 'Binary': 'libsample0 sample-dev sample-udeb' } + end + + with_them do + include_context 'Debian ExtractMetadata Service', params[:trait] do + it_behaves_like 'Test Debian ExtractMetadata Service', + params[:expected_file_type], + params[:expected_architecture], + params[:expected_fields] + end + end + + context 'with invalid package file' do + let(:package_file) { create(:conan_package_file) } + + it 'raise error' do + expect { subject }.to raise_error(described_class::ExtractionError, 'invalid package file') + end + end +end diff --git a/spec/services/packages/debian/get_or_create_incoming_service_spec.rb b/spec/services/packages/debian/get_or_create_incoming_service_spec.rb new file mode 100644 index 00000000000..ab99b091246 --- /dev/null +++ b/spec/services/packages/debian/get_or_create_incoming_service_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::Debian::GetOrCreateIncomingService do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + + subject(:service) { described_class.new(project, user) } + + describe '#execute' do + subject(:package) { service.execute } + + context 'run once' do + it 'creates a new package', :aggregate_failures do + expect(package).to be_valid + expect(package.project_id).to eq(project.id) + expect(package.creator_id).to eq(user.id) + expect(package.name).to eq('incoming') + expect(package.version).to be_nil + expect(package.package_type).to eq('debian') + expect(package.debian_incoming?).to be_truthy + end + + it_behaves_like 'assigns the package creator' + end + + context 'run twice' do + let!(:package2) { service.execute } + + it 'returns the same object' do + expect(package2.id).to eq(package.id) + end + end + end +end diff --git a/spec/services/packages/maven/find_or_create_package_service_spec.rb b/spec/services/packages/maven/find_or_create_package_service_spec.rb index 2eaad7db445..82dffeefcde 100644 --- a/spec/services/packages/maven/find_or_create_package_service_spec.rb +++ b/spec/services/packages/maven/find_or_create_package_service_spec.rb @@ -11,29 +11,36 @@ RSpec.describe Packages::Maven::FindOrCreatePackageService do let(:file_name) { 'test.jar' } let(:param_path) { "#{path}/#{version}" } let(:params) { { path: param_path, file_name: file_name } } + let(:service) { described_class.new(project, user, params) } describe '#execute' do using RSpec::Parameterized::TableSyntax - subject { described_class.new(project, user, params).execute } + subject { service.execute } - RSpec.shared_examples 'reuse existing package' do - it { expect { subject}.not_to change { Packages::Package.count } } + shared_examples 'reuse existing package' do + it { expect { subject }.not_to change { Packages::Package.count } } - it { is_expected.to eq(existing_package) } + it 'returns the existing package' do + expect(subject.payload).to eq(package: existing_package) + end end - RSpec.shared_examples 'create package' do + shared_examples 'create package' do it { expect { subject }.to change { Packages::Package.count }.by(1) } - it 'sets the proper name and version' do - pkg = subject + it 'sets the proper name and version', :aggregate_failures do + pkg = subject.payload[:package] expect(pkg.name).to eq(path) expect(pkg.version).to eq(version) end - it_behaves_like 'assigns build to package' + context 'with a build' do + subject { service.execute.payload[:package] } + + it_behaves_like 'assigns build to package' + end end context 'path with version' do @@ -90,5 +97,36 @@ RSpec.describe Packages::Maven::FindOrCreatePackageService do expect { subject }.to change { Packages::BuildInfo.count }.by(1) end end + + context 'when package duplicates are not allowed' do + let_it_be_with_refind(:package_settings) { create(:namespace_package_setting, :group, maven_duplicates_allowed: false) } + let_it_be_with_refind(:group) { package_settings.namespace } + let_it_be_with_refind(:project) { create(:project, group: group) } + let!(:existing_package) { create(:maven_package, name: path, version: version, project: project) } + + it { expect { subject }.not_to change { project.package_files.count } } + + it 'returns an error', :aggregate_failures do + expect(subject.payload).to be_empty + expect(subject.errors).to include('Duplicate package is not allowed') + end + + context 'when uploading different non-duplicate files to the same package' do + before do + package_file = existing_package.package_files.find_by(file_name: 'my-app-1.0-20180724.124855-1.jar') + package_file.destroy! + end + + it_behaves_like 'reuse existing package' + end + + context 'when the package name matches the exception regex' do + before do + package_settings.update!(maven_duplicate_exception_regex: '.*') + end + + it_behaves_like 'reuse existing package' + end + end end end diff --git a/spec/services/packages/nuget/search_service_spec.rb b/spec/services/packages/nuget/search_service_spec.rb index d163e7087e4..db758dc6672 100644 --- a/spec/services/packages/nuget/search_service_spec.rb +++ b/spec/services/packages/nuget/search_service_spec.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true + require 'spec_helper' RSpec.describe Packages::Nuget::SearchService do - let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:subgroup) { create(:group, parent: group) } + let_it_be(:project) { create(:project, namespace: subgroup) } let_it_be(:package_a) { create(:nuget_package, project: project, name: 'DummyPackageA') } let_it_be(:packages_b) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageB') } let_it_be(:packages_c) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageC') } @@ -16,94 +20,126 @@ RSpec.describe Packages::Nuget::SearchService do let(:options) { { include_prerelease_versions: include_prerelease_versions, per_page: per_page, padding: padding } } describe '#execute' do - subject { described_class.new(project, search_term, options).execute } + subject { described_class.new(user, target, search_term, options).execute } - it { expect_search_results 3, package_a, packages_b, packages_c } + shared_examples 'handling all the conditions' do + it { expect_search_results 3, package_a, packages_b, packages_c } - context 'with a smaller per page count' do - let(:per_page) { 2 } + context 'with a smaller per page count' do + let(:per_page) { 2 } - it { expect_search_results 3, package_a, packages_b } - end + it { expect_search_results 3, package_a, packages_b } + end - context 'with 0 per page count' do - let(:per_page) { 0 } + context 'with 0 per page count' do + let(:per_page) { 0 } - it { expect_search_results 3, [] } - end + it { expect_search_results 3, [] } + end - context 'with a negative per page count' do - let(:per_page) { -1 } + context 'with a negative per page count' do + let(:per_page) { -1 } - it { expect { subject }.to raise_error(ArgumentError, 'negative per_page') } - end + it { expect { subject }.to raise_error(ArgumentError, 'negative per_page') } + end - context 'with a padding' do - let(:padding) { 2 } + context 'with a padding' do + let(:padding) { 2 } - it { expect_search_results 3, packages_c } - end + it { expect_search_results 3, packages_c } + end - context 'with a too big padding' do - let(:padding) { 5 } + context 'with a too big padding' do + let(:padding) { 5 } - it { expect_search_results 3, [] } - end + it { expect_search_results 3, [] } + end - context 'with a negative padding' do - let(:padding) { -1 } + context 'with a negative padding' do + let(:padding) { -1 } - it { expect { subject }.to raise_error(ArgumentError, 'negative padding') } - end + it { expect { subject }.to raise_error(ArgumentError, 'negative padding') } + end - context 'with search term' do - let(:search_term) { 'umm' } + context 'with search term' do + let(:search_term) { 'umm' } - it { expect_search_results 3, package_a, packages_b, packages_c } - end + it { expect_search_results 3, package_a, packages_b, packages_c } + end - context 'with nil search term' do - let(:search_term) { nil } + context 'with nil search term' do + let(:search_term) { nil } - it { expect_search_results 4, package_a, packages_b, packages_c, package_d } - end + it { expect_search_results 4, package_a, packages_b, packages_c, package_d } + end - context 'with empty search term' do - let(:search_term) { '' } + context 'with empty search term' do + let(:search_term) { '' } - it { expect_search_results 4, package_a, packages_b, packages_c, package_d } - end + it { expect_search_results 4, package_a, packages_b, packages_c, package_d } + end - context 'with prefix search term' do - let(:search_term) { 'dummy' } + context 'with prefix search term' do + let(:search_term) { 'dummy' } - it { expect_search_results 3, package_a, packages_b, packages_c } - end + it { expect_search_results 3, package_a, packages_b, packages_c } + end + + context 'with suffix search term' do + let(:search_term) { 'packagec' } + + it { expect_search_results 1, packages_c } + end + + context 'with pre release packages' do + let_it_be(:package_e) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1-alpha') } + + context 'including them' do + it { expect_search_results 4, package_a, packages_b, packages_c, package_e } + end + + context 'excluding them' do + let(:include_prerelease_versions) { false } - context 'with suffix search term' do - let(:search_term) { 'packagec' } + it { expect_search_results 3, package_a, packages_b, packages_c } - it { expect_search_results 1, packages_c } + context 'when mixed with release versions' do + let_it_be(:package_e_release) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1') } + + it { expect_search_results 4, package_a, packages_b, packages_c, package_e_release } + end + end + end end - context 'with pre release packages' do - let_it_be(:package_e) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1-alpha') } + context 'with project' do + let(:target) { project } - context 'including them' do - it { expect_search_results 4, package_a, packages_b, packages_c, package_e } + before do + project.add_developer(user) end - context 'excluding them' do - let(:include_prerelease_versions) { false } + it_behaves_like 'handling all the conditions' + end - it { expect_search_results 3, package_a, packages_b, packages_c } + context 'with subgroup' do + let(:target) { subgroup } - context 'when mixed with release versions' do - let_it_be(:package_e_release) { create(:nuget_package, project: project, name: 'DummyPackageE', version: '3.2.1') } + before do + subgroup.add_developer(user) + end - it { expect_search_results 4, package_a, packages_b, packages_c, package_e_release } - end + it_behaves_like 'handling all the conditions' + end + + context 'with group' do + let(:target) { group } + + before do + group.add_developer(user) end + + it_behaves_like 'handling all the conditions' end def expect_search_results(total_count, *results) diff --git a/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb b/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb new file mode 100644 index 00000000000..29023621413 --- /dev/null +++ b/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Pages::MigrateLegacyStorageToDeploymentService do + let(:project) { create(:project, :repository) } + let(:service) { described_class.new(project) } + + it 'marks pages as not deployed if public directory is absent' do + project.mark_pages_as_deployed + + expect(project.pages_metadatum.reload.deployed).to eq(true) + + expect(service.execute).to( + eq(status: :error, + message: "Can't create zip archive: Can not find valid public dir in #{project.pages_path}") + ) + + expect(project.pages_metadatum.reload.deployed).to eq(false) + end + + it 'does not mark pages as not deployed if public directory is absent but pages_deployment exists' do + deployment = create(:pages_deployment, project: project) + project.update_pages_deployment!(deployment) + project.mark_pages_as_deployed + + expect(project.pages_metadatum.reload.deployed).to eq(true) + + expect(service.execute).to( + eq(status: :error, + message: "Can't create zip archive: Can not find valid public dir in #{project.pages_path}") + ) + + expect(project.pages_metadatum.reload.deployed).to eq(true) + end + + it 'does not mark pages as not deployed if public directory is absent but feature is disabled' do + stub_feature_flags(pages_migration_mark_as_not_deployed: false) + + project.mark_pages_as_deployed + + expect(project.pages_metadatum.reload.deployed).to eq(true) + + expect(service.execute).to( + eq(status: :error, + message: "Can't create zip archive: Can not find valid public dir in #{project.pages_path}") + ) + + expect(project.pages_metadatum.reload.deployed).to eq(true) + end + + it 'removes pages archive when can not save deployment' do + archive = fixture_file_upload("spec/fixtures/pages.zip") + expect_next_instance_of(::Pages::ZipDirectoryService) do |zip_service| + expect(zip_service).to receive(:execute).and_return(status: :success, + archive_path: archive.path, + entries_count: 3) + end + + expect_next_instance_of(PagesDeployment) do |deployment| + expect(deployment).to receive(:save!).and_raise("Something") + end + + expect do + service.execute + end.to raise_error("Something") + + expect(File.exist?(archive.path)).to eq(false) + end + + context 'when pages site is deployed to legacy storage' do + before do + FileUtils.mkdir_p File.join(project.pages_path, "public") + File.open(File.join(project.pages_path, "public/index.html"), "w") do |f| + f.write("Hello!") + end + end + + it 'creates pages deployment' do + expect do + expect(described_class.new(project).execute).to eq(status: :success) + end.to change { project.reload.pages_deployments.count }.by(1) + + deployment = project.pages_metadatum.pages_deployment + + Zip::File.open(deployment.file.path) do |zip_file| + expect(zip_file.glob("public").first.ftype).to eq(:directory) + expect(zip_file.glob("public/index.html").first.get_input_stream.read).to eq("Hello!") + end + + expect(deployment.file_count).to eq(2) + expect(deployment.file_sha256).to eq(Digest::SHA256.file(deployment.file.path).hexdigest) + end + + it 'removes tmp pages archive' do + described_class.new(project).execute + + expect(File.exist?(File.join(project.pages_path, '@migrated.zip'))).to eq(false) + end + + it 'does not change pages deployment if it is set' do + old_deployment = create(:pages_deployment, project: project) + project.update_pages_deployment!(old_deployment) + + expect do + described_class.new(project).execute + end.not_to change { project.pages_metadatum.reload.pages_deployment_id }.from(old_deployment.id) + end + + it 'raises exception if exclusive lease is taken' do + described_class.new(project).try_obtain_lease do + expect do + described_class.new(project).execute + end.to raise_error(described_class::ExclusiveLeaseTakenError) + end + end + end +end diff --git a/spec/services/pages/zip_directory_service_spec.rb b/spec/services/pages/zip_directory_service_spec.rb index 1568103d102..dcab6b2dada 100644 --- a/spec/services/pages/zip_directory_service_spec.rb +++ b/spec/services/pages/zip_directory_service_spec.rb @@ -14,19 +14,35 @@ RSpec.describe Pages::ZipDirectoryService do described_class.new(@work_dir).execute end - let(:archive) { result.first } - let(:entries_count) { result.second } + let(:status) { result[:status] } + let(:message) { result[:message] } + let(:archive) { result[:archive_path] } + let(:entries_count) { result[:entries_count] } + + it 'returns error if project pages dir does not exist' do + expect( + described_class.new("/tmp/not/existing/dir").execute + ).to eq(status: :error, message: "Can not find valid public dir in /tmp/not/existing/dir") + end + + it 'returns nils if there is no public directory and does not leave archive' do + expect(status).to eq(:error) + expect(message).to eq("Can not find valid public dir in #{@work_dir}") + expect(archive).to eq(nil) + expect(entries_count).to eq(nil) - it 'raises error if there is no public directory' do - expect { archive }.to raise_error(described_class::InvalidArchiveError) + expect(File.exist?(File.join(@work_dir, '@migrated.zip'))).to eq(false) end - it 'raises error if public directory is a symlink' do + it 'returns nils if public directory is a symlink' do create_dir('target') create_file('./target/index.html', 'hello') create_link("public", "./target") - expect { archive }.to raise_error(described_class::InvalidArchiveError) + expect(status).to eq(:error) + expect(message).to eq("Can not find valid public dir in #{@work_dir}") + expect(archive).to eq(nil) + expect(entries_count).to eq(nil) end context 'when there is a public directory' do diff --git a/spec/services/post_receive_service_spec.rb b/spec/services/post_receive_service_spec.rb index 4e303bfc20a..6e2cd7edf04 100644 --- a/spec/services/post_receive_service_spec.rb +++ b/spec/services/post_receive_service_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe PostReceiveService do include Gitlab::Routing + include AfterNextHelpers let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) } @@ -46,8 +47,8 @@ RSpec.describe PostReceiveService do expect(subject).to be_empty end - it 'does not record a namespace onboarding progress action' do - expect(NamespaceOnboardingAction).not_to receive(:create_action) + it 'does not record an onboarding progress action' do + expect_next(OnboardingProgressService).not_to receive(:execute) subject end @@ -87,9 +88,9 @@ RSpec.describe PostReceiveService do expect(response.reference_counter_decreased).to be(true) end - it 'records a namespace onboarding progress action' do - expect(NamespaceOnboardingAction).to receive(:create_action) - .with(project.namespace, :git_write) + it 'records an onboarding progress action' do + expect_next(OnboardingProgressService, project.namespace) + .to receive(:execute).with(action: :git_write) subject end diff --git a/spec/services/projects/after_import_service_spec.rb b/spec/services/projects/after_import_service_spec.rb index a109348ea19..a16aec891a9 100644 --- a/spec/services/projects/after_import_service_spec.rb +++ b/spec/services/projects/after_import_service_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Projects::AfterImportService do describe '#execute' do before do - allow(Projects::HousekeepingService) + allow(Repositories::HousekeepingService) .to receive(:new).with(project).and_return(housekeeping_service) allow(housekeeping_service) @@ -73,10 +73,10 @@ RSpec.describe Projects::AfterImportService do end context 'when housekeeping service lease is taken' do - let(:exception) { Projects::HousekeepingService::LeaseTaken.new } + let(:exception) { Repositories::HousekeepingService::LeaseTaken.new } it 'logs the error message' do - allow_next_instance_of(Projects::HousekeepingService) do |instance| + allow_next_instance_of(Repositories::HousekeepingService) do |instance| expect(instance).to receive(:execute).and_raise(exception) end diff --git a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb index 8ddcb8ce660..17c2f0f6c17 100644 --- a/spec/services/projects/container_repository/cleanup_tags_service_spec.rb +++ b/spec/services/projects/container_repository/cleanup_tags_service_spec.rb @@ -3,11 +3,14 @@ require 'spec_helper' RSpec.describe Projects::ContainerRepository::CleanupTagsService do + using RSpec::Parameterized::TableSyntax + let_it_be(:user) { create(:user) } let_it_be(:project, reload: true) { create(:project, :private) } let_it_be(:repository) { create(:container_repository, :root, project: project) } let(:service) { described_class.new(project, user, params) } + let(:tags) { %w[latest A Ba Bb C D E] } before do project.add_maintainer(user) @@ -16,7 +19,8 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do stub_container_registry_tags( repository: repository.path, - tags: %w(latest A Ba Bb C D E)) + tags: tags + ) stub_tag_digest('latest', 'sha256:configA') stub_tag_digest('A', 'sha256:configA') @@ -30,6 +34,8 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do stub_digest_config('sha256:configB', 5.days.ago) stub_digest_config('sha256:configC', 1.month.ago) stub_digest_config('sha256:configD', nil) + + stub_feature_flags(container_registry_expiration_policies_throttling: false) end describe '#execute' do @@ -42,7 +48,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do expect_any_instance_of(Projects::ContainerRepository::DeleteTagsService) .not_to receive(:execute) - is_expected.to include(status: :success, deleted: []) + is_expected.to eq(expected_service_response(before_truncate_size: 0, after_truncate_size: 0, before_delete_size: 0)) end end @@ -51,7 +57,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do it 'does remove all tags except latest' do expect_delete(%w(A Ba Bb C D E)) - is_expected.to include(status: :success, deleted: %w(A Ba Bb C D E)) + is_expected.to eq(expected_service_response(deleted: %w(A Ba Bb C D E))) end end @@ -78,12 +84,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do subject end - it 'returns an error' do - response = subject - - expect(response[:status]).to eq(:error) - expect(response[:message]).to eq('invalid regex') - end + it { is_expected.to eq(status: :error, message: 'invalid regex') } it 'calls error tracking service' do expect(Gitlab::ErrorTracking).to receive(:log_exception).and_call_original @@ -119,7 +120,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do it 'does remove C and D' do expect_delete(%w(C D)) - is_expected.to include(status: :success, deleted: %w(C D)) + is_expected.to eq(expected_service_response(deleted: %w(C D), before_truncate_size: 2, after_truncate_size: 2, before_delete_size: 2)) end context 'with overriding allow regex' do @@ -131,7 +132,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do it 'does not remove C' do expect_delete(%w(D)) - is_expected.to include(status: :success, deleted: %w(D)) + is_expected.to eq(expected_service_response(deleted: %w(D), before_truncate_size: 1, after_truncate_size: 1, before_delete_size: 1)) end end @@ -144,7 +145,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do it 'does not remove C' do expect_delete(%w(D)) - is_expected.to include(status: :success, deleted: %w(D)) + is_expected.to eq(expected_service_response(deleted: %w(D), before_truncate_size: 1, after_truncate_size: 1, before_delete_size: 1)) end end end @@ -158,7 +159,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do it 'does not remove B*' do expect_delete(%w(A C D E)) - is_expected.to include(status: :success, deleted: %w(A C D E)) + is_expected.to eq(expected_service_response(deleted: %w(A C D E), before_truncate_size: 4, after_truncate_size: 4, before_delete_size: 4)) end end @@ -173,7 +174,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do expect(service).to receive(:order_by_date).and_call_original - is_expected.to include(status: :success, deleted: %w(Bb Ba C)) + is_expected.to eq(expected_service_response(deleted: %w(Bb Ba C), before_truncate_size: 4, after_truncate_size: 4, before_delete_size: 3)) end end @@ -187,7 +188,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do expect(service).not_to receive(:order_by_date) - is_expected.to include(status: :success, deleted: %w(A Ba Bb C)) + is_expected.to eq(expected_service_response(deleted: %w(A Ba Bb C), before_truncate_size: 4, after_truncate_size: 4, before_delete_size: 4)) end end @@ -200,7 +201,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do it 'does remove B* and C as they are the oldest' do expect_delete(%w(Bb Ba C)) - is_expected.to include(status: :success, deleted: %w(Bb Ba C)) + is_expected.to eq(expected_service_response(deleted: %w(Bb Ba C), before_delete_size: 3)) end end @@ -213,7 +214,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do it 'does remove B* and C as they are older than 1 day' do expect_delete(%w(Ba Bb C)) - is_expected.to include(status: :success, deleted: %w(Ba Bb C)) + is_expected.to eq(expected_service_response(deleted: %w(Ba Bb C), before_delete_size: 3)) end end @@ -227,7 +228,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do it 'does remove B* and C' do expect_delete(%w(Bb Ba C)) - is_expected.to include(status: :success, deleted: %w(Bb Ba C)) + is_expected.to eq(expected_service_response(deleted: %w(Bb Ba C), before_delete_size: 3)) end end @@ -245,7 +246,7 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do it 'succeeds without a user' do expect_delete(%w(Bb Ba C), container_expiration_policy: true) - is_expected.to include(status: :success, deleted: %w(Bb Ba C)) + is_expected.to eq(expected_service_response(deleted: %w(Bb Ba C), before_delete_size: 3)) end end @@ -257,10 +258,73 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do end it 'fails' do - is_expected.to include(status: :error, message: 'access denied') + is_expected.to eq(status: :error, message: 'access denied') end end end + + context 'truncating the tags list' do + let(:params) do + { + 'name_regex_delete' => '.*', + 'keep_n' => 1 + } + end + + shared_examples 'returning the response' do |status:, original_size:, before_truncate_size:, after_truncate_size:, before_delete_size:| + it 'returns the response' do + result = subject + + service_response = expected_service_response( + status: status, + original_size: original_size, + before_truncate_size: before_truncate_size, + after_truncate_size: after_truncate_size, + before_delete_size: before_delete_size, + deleted: nil + ) + + expect(result).to eq(service_response.compact) + end + end + + where(:feature_flag_enabled, :max_list_size, :delete_tags_service_status, :expected_status, :expected_truncated) do + false | 10 | :success | :success | false + false | 10 | :error | :error | false + false | 3 | :success | :success | false + false | 3 | :error | :error | false + false | 0 | :success | :success | false + false | 0 | :error | :error | false + true | 10 | :success | :success | false + true | 10 | :error | :error | false + true | 3 | :success | :error | true + true | 3 | :error | :error | true + true | 0 | :success | :success | false + true | 0 | :error | :error | false + end + + with_them do + before do + stub_feature_flags(container_registry_expiration_policies_throttling: feature_flag_enabled) + stub_application_setting(container_registry_cleanup_tags_service_max_list_size: max_list_size) + allow_next_instance_of(Projects::ContainerRepository::DeleteTagsService) do |service| + expect(service).to receive(:execute).and_return(status: delete_tags_service_status) + end + end + + original_size = 7 + keep_n = 1 + + it_behaves_like( + 'returning the response', + status: params[:expected_status], + original_size: original_size, + before_truncate_size: original_size - keep_n, + after_truncate_size: params[:expected_truncated] ? params[:max_list_size] + keep_n : original_size - keep_n, + before_delete_size: params[:expected_truncated] ? params[:max_list_size] : original_size - keep_n - 1 # one tag is filtered out with older_than filter + ) + end + end end private @@ -295,4 +359,16 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService do .to receive(:execute) .with(repository) { { status: :success, deleted: tags } } end + + # all those -1 because the default tags on L13 have a "latest" that will be filtered out + def expected_service_response(status: :success, deleted: [], original_size: tags.size, before_truncate_size: tags.size - 1, after_truncate_size: tags.size - 1, before_delete_size: tags.size - 1) + { + status: status, + deleted: deleted, + original_size: original_size, + before_truncate_size: before_truncate_size, + after_truncate_size: after_truncate_size, + before_delete_size: before_delete_size + }.compact + end end diff --git a/spec/services/projects/container_repository/delete_tags_service_spec.rb b/spec/services/projects/container_repository/delete_tags_service_spec.rb index 4da416d9698..94037d6de1e 100644 --- a/spec/services/projects/container_repository/delete_tags_service_spec.rb +++ b/spec/services/projects/container_repository/delete_tags_service_spec.rb @@ -119,9 +119,9 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do end end - it { is_expected.to include(status: :error, message: 'timeout while deleting tags') } + it { is_expected.to include(status: :error, message: 'error while deleting tags') } - it_behaves_like 'logging an error response', message: 'timeout while deleting tags', extra_log: { deleted_tags_count: 0 } + it_behaves_like 'logging an error response', message: 'error while deleting tags', extra_log: { deleted_tags_count: 0 } end end end diff --git a/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb b/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb index 988971171fc..74f782538c5 100644 --- a/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb +++ b/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb @@ -67,7 +67,7 @@ RSpec.describe Projects::ContainerRepository::Gitlab::DeleteTagsService do stub_delete_reference_requests('A' => 200) end - it { is_expected.to eq(status: :error, message: 'timeout while deleting tags', deleted: ['A']) } + it { is_expected.to eq(status: :error, message: 'error while deleting tags', deleted: ['A'], exception_class_name: Projects::ContainerRepository::Gitlab::DeleteTagsService::TimeoutError.name) } it 'tracks the exception' do expect(::Gitlab::ErrorTracking) @@ -89,6 +89,21 @@ RSpec.describe Projects::ContainerRepository::Gitlab::DeleteTagsService do it_behaves_like 'deleting tags' end end + + context 'with a network error' do + before do + expect(service).to receive(:delete_tags).and_raise(::Faraday::TimeoutError) + end + + it { is_expected.to eq(status: :error, message: 'error while deleting tags', deleted: [], exception_class_name: ::Faraday::TimeoutError.name) } + + it 'tracks the exception' do + expect(::Gitlab::ErrorTracking) + .to receive(:track_exception).with(::Faraday::TimeoutError, tags_count: tags.size, container_repository_id: repository.id) + + subject + end + end end end diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 60dfee820ca..a11f16573f5 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -116,6 +116,24 @@ RSpec.describe Projects::ForkService do expect(to_project.fork_network_member.forked_from_project).to eq(from_forked_project) end + context 'when the forked project has higher visibility than the root project' do + let(:root_project) { create(:project, :public) } + + it 'successfully creates a fork of the fork with correct visibility' do + forked_project = fork_project(root_project, @to_user, using_service: true) + + root_project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + + # Forked project visibility is not affected by root project visibility change + expect(forked_project).to have_attributes(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + + fork_of_the_fork = fork_project(forked_project, @to_user, namespace: other_namespace, using_service: true) + + expect(fork_of_the_fork).to be_valid + expect(fork_of_the_fork).to have_attributes(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + end + end + it_behaves_like 'forks count cache refresh' do let(:from_project) { from_forked_project } let(:to_user) { @to_user } diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb index 18871f010f8..0aa4a1cd312 100644 --- a/spec/services/projects/housekeeping_service_spec.rb +++ b/spec/services/projects/housekeeping_service_spec.rb @@ -2,127 +2,19 @@ require 'spec_helper' +# This is a compatibility class to avoid calling a non-existent +# class from sidekiq during deployment. +# +# We're deploying the name of the referenced class in 13.9. Nevertheless, +# we cannot remove the class entirely because there can be jobs +# referencing it. We still need this specs to ensure that the old +# class still has the old behavior. +# +# We can get rid of this class in 13.10 +# https://gitlab.com/gitlab-org/gitlab/-/issues/297580 +# RSpec.describe Projects::HousekeepingService do - subject { described_class.new(project) } - - let_it_be(:project) { create(:project, :repository) } - - before do - project.reset_pushes_since_gc - end - - after do - project.reset_pushes_since_gc - end - - describe '#execute' do - it 'enqueues a sidekiq job' do - expect(subject).to receive(:try_obtain_lease).and_return(:the_uuid) - expect(subject).to receive(:lease_key).and_return(:the_lease_key) - expect(subject).to receive(:task).and_return(:incremental_repack) - expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :incremental_repack, :the_lease_key, :the_uuid).and_call_original - - Sidekiq::Testing.fake! do - expect { subject.execute }.to change(GitGarbageCollectWorker.jobs, :size).by(1) - end - end - - it 'yields the block if given' do - expect do |block| - subject.execute(&block) - end.to yield_with_no_args - end - - it 'resets counter after execution' do - expect(subject).to receive(:try_obtain_lease).and_return(:the_uuid) - allow(subject).to receive(:gc_period).and_return(1) - project.increment_pushes_since_gc - - perform_enqueued_jobs do - expect { subject.execute }.to change { project.pushes_since_gc }.to(0) - end - end - - context 'when no lease can be obtained' do - before do - expect(subject).to receive(:try_obtain_lease).and_return(false) - end - - it 'does not enqueue a job' do - expect(GitGarbageCollectWorker).not_to receive(:perform_async) - - expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken) - end - - it 'does not reset pushes_since_gc' do - expect do - expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken) - end.not_to change { project.pushes_since_gc } - end - - it 'does not yield' do - expect do |block| - expect { subject.execute(&block) } - .to raise_error(Projects::HousekeepingService::LeaseTaken) - end.not_to yield_with_no_args - end - end - - context 'task type' do - it 'goes through all three housekeeping tasks, executing only the highest task when there is overlap' do - allow(subject).to receive(:try_obtain_lease).and_return(:the_uuid) - allow(subject).to receive(:lease_key).and_return(:the_lease_key) - - # At push 200 - expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :gc, :the_lease_key, :the_uuid) - .once - # At push 50, 100, 150 - expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :full_repack, :the_lease_key, :the_uuid) - .exactly(3).times - # At push 10, 20, ... (except those above) - expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :incremental_repack, :the_lease_key, :the_uuid) - .exactly(16).times - # At push 6, 12, 18, ... (except those above) - expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :pack_refs, :the_lease_key, :the_uuid) - .exactly(27).times - - 201.times do - subject.increment! - subject.execute if subject.needed? - end - - expect(project.pushes_since_gc).to eq(1) - end - end - - it 'runs the task specifically requested' do - housekeeping = described_class.new(project, :gc) - - allow(housekeeping).to receive(:try_obtain_lease).and_return(:gc_uuid) - allow(housekeeping).to receive(:lease_key).and_return(:gc_lease_key) - - expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :gc, :gc_lease_key, :gc_uuid).twice - - 2.times do - housekeeping.execute - end - end - end - - describe '#needed?' do - it 'when the count is low enough' do - expect(subject.needed?).to eq(false) - end - - it 'when the count is high enough' do - allow(project).to receive(:pushes_since_gc).and_return(10) - expect(subject.needed?).to eq(true) - end - end - - describe '#increment!' do - it 'increments the pushes_since_gc counter' do - expect { subject.increment! }.to change { project.pushes_since_gc }.by(1) - end + it_behaves_like 'housekeeps repository' do + let_it_be(:resource) { create(:project, :repository) } end end diff --git a/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb b/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb index 5b76386bfab..15c9d1e5925 100644 --- a/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb +++ b/spec/services/projects/schedule_bulk_repository_shard_moves_service_spec.rb @@ -3,45 +3,10 @@ require 'spec_helper' RSpec.describe Projects::ScheduleBulkRepositoryShardMovesService do - before do - stub_storage_settings('test_second_storage' => { 'path' => 'tmp/tests/extra_storage' }) - end - - let!(:project) { create(:project, :repository).tap { |project| project.track_project_repository } } - let(:source_storage_name) { 'default' } - let(:destination_storage_name) { 'test_second_storage' } - - describe '#execute' do - it 'schedules project repository storage moves' do - expect { subject.execute(source_storage_name, destination_storage_name) } - .to change(ProjectRepositoryStorageMove, :count).by(1) - - storage_move = project.repository_storage_moves.last! - - expect(storage_move).to have_attributes( - source_storage_name: source_storage_name, - destination_storage_name: destination_storage_name, - state_name: :scheduled - ) - end - - context 'read-only repository' do - let!(:project) { create(:project, :repository, :read_only).tap { |project| project.track_project_repository } } - - it 'does not get scheduled' do - expect(subject).to receive(:log_info) - .with("Project #{project.full_path} (#{project.id}) was skipped: Project is read only") - expect { subject.execute(source_storage_name, destination_storage_name) } - .to change(ProjectRepositoryStorageMove, :count).by(0) - end - end - end - - describe '.enqueue' do - it 'defers to the worker' do - expect(::ProjectScheduleBulkRepositoryShardMovesWorker).to receive(:perform_async).with(source_storage_name, destination_storage_name) + it_behaves_like 'moves repository shard in bulk' do + let_it_be_with_reload(:container) { create(:project, :repository).tap { |project| project.track_project_repository } } - described_class.enqueue(source_storage_name, destination_storage_name) - end + let(:move_service_klass) { ProjectRepositoryStorageMove } + let(:bulk_worker_klass) { ::ProjectScheduleBulkRepositoryShardMovesWorker } end end diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index a15f6bdbe2c..a6730c5de52 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -79,6 +79,19 @@ RSpec.describe Projects::UpdatePagesService do end end + it 'fails if sha on branch was updated before deployment was uploaded' do + expect(subject).to receive(:create_pages_deployment).and_wrap_original do |m, *args| + build.update!(ref: 'feature') + m.call(*args) + end + + expect(execute).not_to eq(:success) + expect(project.pages_metadatum).not_to be_deployed + + expect(deploy_status).to be_failed + expect(deploy_status.description).to eq('build SHA is outdated for this ref') + end + it 'does not fail if pages_metadata is absent' do project.pages_metadatum.destroy! project.reload diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 760cd85bf71..a59b6adf346 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -15,13 +15,6 @@ RSpec.describe Projects::UpdateService do let(:admin) { create(:admin) } context 'when changing visibility level' do - def expect_to_call_unlink_fork_service - service = Projects::UnlinkForkService.new(project, user) - - expect(Projects::UnlinkForkService).to receive(:new).with(project, user).and_return(service) - expect(service).to receive(:execute).and_call_original - end - context 'when visibility_level changes to INTERNAL' do it 'updates the project to internal' do expect(TodosDestroyer::ProjectPrivateWorker).not_to receive(:perform_in) @@ -31,18 +24,6 @@ RSpec.describe Projects::UpdateService do expect(result).to eq({ status: :success }) expect(project).to be_internal end - - context 'and project is PUBLIC' do - before do - project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) - end - - it 'unlinks project from fork network' do - expect_to_call_unlink_fork_service - - update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL) - end - end end context 'when visibility_level changes to PUBLIC' do @@ -78,30 +59,6 @@ RSpec.describe Projects::UpdateService do expect(result).to eq({ status: :success }) expect(project).to be_private end - - context 'and project is PUBLIC' do - before do - project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) - end - - it 'unlinks project from fork network' do - expect_to_call_unlink_fork_service - - update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE) - end - end - - context 'and project is INTERNAL' do - before do - project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) - end - - it 'unlinks project from fork network' do - expect_to_call_unlink_fork_service - - update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE) - end - end end context 'when visibility levels are restricted to PUBLIC only' do diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index e6d1d0e90a7..21e294418a1 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -8,6 +8,7 @@ RSpec.describe QuickActions::InterpretService do let_it_be(:project) { public_project } let_it_be(:developer) { create(:user) } let_it_be(:developer2) { create(:user) } + let_it_be(:developer3) { create(:user) } let_it_be_with_reload(:issue) { create(:issue, project: project) } let(:milestone) { create(:milestone, project: project, title: '9.10') } let(:commit) { create(:commit, project: project) } @@ -23,6 +24,7 @@ RSpec.describe QuickActions::InterpretService do before do stub_licensed_features(multiple_issue_assignees: false, + multiple_merge_request_reviewers: false, multiple_merge_request_assignees: false) end @@ -665,6 +667,24 @@ RSpec.describe QuickActions::InterpretService do end end + shared_examples 'assign_reviewer command' do + it 'assigns a reviewer to a single user' do + _, updates, message = service.execute(content, issuable) + + expect(updates).to eq(reviewer_ids: [developer.id]) + expect(message).to eq("Assigned #{developer.to_reference} as reviewer.") + end + end + + shared_examples 'unassign_reviewer command' do + it 'removes a single reviewer' do + _, updates, message = service.execute(content, issuable) + + expect(updates).to eq(reviewer_ids: []) + expect(message).to eq("Removed reviewer #{developer.to_reference}.") + end + end + it_behaves_like 'reopen command' do let(:content) { '/reopen' } let(:issuable) { issue } @@ -779,6 +799,11 @@ RSpec.describe QuickActions::InterpretService do it_behaves_like 'assign command' do let(:content) { "/assign @#{developer.username}" } + let(:issuable) { create(:incident, project: project) } + end + + it_behaves_like 'assign command' do + let(:content) { "/assign @#{developer.username}" } let(:issuable) { merge_request } end end @@ -789,12 +814,32 @@ RSpec.describe QuickActions::InterpretService do project.add_developer(developer2) end - it_behaves_like 'assign command' do + # There's no guarantee that the reference extractor will preserve + # the order of the mentioned users since this is dependent on the + # order in which rows are returned. We just ensure that at least + # one of the mentioned users is assigned. + shared_examples 'assigns to one of the two users' do + let(:content) { "/assign @#{developer.username} @#{developer2.username}" } + + it 'assigns to a single user' do + _, updates, message = service.execute(content, issuable) + + expect(updates[:assignee_ids].count).to eq(1) + assignee = updates[:assignee_ids].first + expect([developer.id, developer2.id]).to include(assignee) + + user = assignee == developer.id ? developer : developer2 + + expect(message).to match("Assigned #{user.to_reference}.") + end + end + + it_behaves_like 'assigns to one of the two users' do let(:content) { "/assign @#{developer.username} @#{developer2.username}" } let(:issuable) { issue } end - it_behaves_like 'assign command', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27989' do + it_behaves_like 'assigns to one of the two users' do let(:content) { "/assign @#{developer.username} @#{developer2.username}" } let(:issuable) { merge_request } end @@ -834,6 +879,142 @@ RSpec.describe QuickActions::InterpretService do let(:issuable) { issue } end + context 'when the merge_request_reviewers flag is enabled' do + describe 'assign_reviewer command' do + let(:content) { "/assign_reviewer @#{developer.username}" } + let(:issuable) { merge_request } + + context 'with one user' do + it_behaves_like 'assign_reviewer command' + end + + context 'with an issue instead of a merge request' do + let(:issuable) { issue } + + it_behaves_like 'empty command' + end + + # CE does not have multiple reviewers + context 'assign command with multiple assignees' do + before do + project.add_developer(developer2) + end + + # There's no guarantee that the reference extractor will preserve + # the order of the mentioned users since this is dependent on the + # order in which rows are returned. We just ensure that at least + # one of the mentioned users is assigned. + context 'assigns to one of the two users' do + let(:content) { "/assign_reviewer @#{developer.username} @#{developer2.username}" } + + it 'assigns to a single reviewer' do + _, updates, message = service.execute(content, issuable) + + expect(updates[:reviewer_ids].count).to eq(1) + reviewer = updates[:reviewer_ids].first + expect([developer.id, developer2.id]).to include(reviewer) + + user = reviewer == developer.id ? developer : developer2 + + expect(message).to match("Assigned #{user.to_reference} as reviewer.") + end + end + end + + context 'with "me" alias' do + let(:content) { '/assign_reviewer me' } + + it_behaves_like 'assign_reviewer command' + end + + context 'with an alias and whitespace' do + let(:content) { '/assign_reviewer me ' } + + it_behaves_like 'assign_reviewer command' + end + + context 'with an incorrect user' do + let(:content) { '/assign_reviewer @abcd1234' } + + it_behaves_like 'empty command', "Failed to assign a reviewer because no user was found." + end + + context 'with the "reviewer" alias' do + let(:content) { "/reviewer @#{developer.username}" } + + it_behaves_like 'assign_reviewer command' + end + + context 'with no user' do + let(:content) { '/assign_reviewer' } + + it_behaves_like 'empty command', "Failed to assign a reviewer because no user was found." + end + + context 'includes only the user reference with extra text' do + let(:content) { "/assign_reviewer @#{developer.username} do it!" } + + it_behaves_like 'assign_reviewer command' + end + end + + describe 'unassign_reviewer command' do + # CE does not have multiple reviewers, so basically anything + # after /unassign_reviewer (including whitespace) will remove + # all the current reviewers. + let(:issuable) { create(:merge_request, reviewers: [developer]) } + let(:content) { "/unassign_reviewer @#{developer.username}" } + + context 'with one user' do + it_behaves_like 'unassign_reviewer command' + end + + context 'with an issue instead of a merge request' do + let(:issuable) { issue } + + it_behaves_like 'empty command' + end + + context 'with anything after the command' do + let(:content) { '/unassign_reviewer supercalifragilisticexpialidocious' } + + it_behaves_like 'unassign_reviewer command' + end + + context 'with the "remove_reviewer" alias' do + let(:content) { "/remove_reviewer @#{developer.username}" } + + it_behaves_like 'unassign_reviewer command' + end + + context 'with no user' do + let(:content) { '/unassign_reviewer' } + + it_behaves_like 'unassign_reviewer command' + end + end + end + + context 'when the merge_request_reviewers flag is disabled' do + before do + stub_feature_flags(merge_request_reviewers: false) + end + + describe 'assign_reviewer command' do + it_behaves_like 'empty command' do + let(:content) { "/assign_reviewer @#{developer.username}" } + let(:issuable) { merge_request } + end + end + + describe 'unassign_reviewer command' do + it_behaves_like 'empty command' do + let(:content) { "/unassign_reviewer @#{developer.username}" } + let(:issuable) { merge_request } + end + end + end + context 'unassign command' do let(:content) { '/unassign' } @@ -1117,6 +1298,11 @@ RSpec.describe QuickActions::InterpretService do let(:issuable) { issue } end + it_behaves_like 'confidential command' do + let(:content) { '/confidential' } + let(:issuable) { create(:incident, project: project) } + end + it_behaves_like 'lock command' do let(:content) { '/lock' } let(:issuable) { issue } @@ -1819,6 +2005,28 @@ RSpec.describe QuickActions::InterpretService do end end + describe 'unassign_reviewer command' do + let(:content) { '/unassign_reviewer' } + let(:merge_request) { create(:merge_request, source_project: project, reviewers: [developer]) } + + it 'includes current assignee reference' do + _, explanations = service.explain(content, merge_request) + + expect(explanations).to eq(["Removes reviewer @#{developer.username}."]) + end + end + + describe 'assign_reviewer command' do + let(:content) { "/assign_reviewer #{developer.to_reference}" } + let(:merge_request) { create(:merge_request, source_project: project, assignees: [developer]) } + + it 'includes only the user reference' do + _, explanations = service.explain(content, merge_request) + + expect(explanations).to eq(["Assigns #{developer.to_reference} as reviewer."]) + end + end + describe 'milestone command' do let(:content) { '/milestone %wrong-milestone' } let!(:milestone) { create(:milestone, project: project, title: '9.10') } diff --git a/spec/services/repositories/housekeeping_service_spec.rb b/spec/services/repositories/housekeeping_service_spec.rb new file mode 100644 index 00000000000..fbd9affb33c --- /dev/null +++ b/spec/services/repositories/housekeeping_service_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Repositories::HousekeepingService do + it_behaves_like 'housekeeps repository' do + let_it_be(:resource) { create(:project, :repository) } + end + + it_behaves_like 'housekeeps repository' do + let_it_be(:project) { create(:project, :wiki_repo) } + let_it_be(:resource) { project.wiki } + end +end diff --git a/spec/services/resource_events/change_state_service_spec.rb b/spec/services/resource_events/change_state_service_spec.rb index 5b5379b241b..255ee9eca57 100644 --- a/spec/services/resource_events/change_state_service_spec.rb +++ b/spec/services/resource_events/change_state_service_spec.rb @@ -30,6 +30,15 @@ RSpec.describe ResourceEvents::ChangeStateService do expect_event_source(event, source) end + + it "sets the created_at timestamp from the system_note_timestamp" do + resource.system_note_timestamp = Time.at(43).utc + + described_class.new(user: user, resource: resource).execute(status: state, mentionable_source: source) + event = resource.resource_state_events.last + + expect(event.created_at).to eq(Time.at(43).utc) + end end end diff --git a/spec/services/service_desk_settings/update_service_spec.rb b/spec/services/service_desk_settings/update_service_spec.rb index fbef587365d..72134af1369 100644 --- a/spec/services/service_desk_settings/update_service_spec.rb +++ b/spec/services/service_desk_settings/update_service_spec.rb @@ -16,19 +16,6 @@ RSpec.describe ServiceDeskSettings::UpdateService do expect(settings.reload.outgoing_name).to eq 'some name' expect(settings.reload.project_key).to eq 'foo' end - - context 'when service_desk_custom_address is disabled' do - before do - stub_feature_flags(service_desk_custom_address: false) - end - - it 'ignores project_key parameter' do - result = described_class.new(settings.project, user, params).execute - - expect(result[:status]).to eq :success - expect(settings.reload.project_key).to be_nil - end - end end context 'when project_key is an empty string' do diff --git a/spec/services/snippets/schedule_bulk_repository_shard_moves_service_spec.rb b/spec/services/snippets/schedule_bulk_repository_shard_moves_service_spec.rb new file mode 100644 index 00000000000..764c7f94a46 --- /dev/null +++ b/spec/services/snippets/schedule_bulk_repository_shard_moves_service_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Snippets::ScheduleBulkRepositoryShardMovesService do + it_behaves_like 'moves repository shard in bulk' do + let_it_be_with_reload(:container) { create(:snippet, :repository) } + + let(:move_service_klass) { SnippetRepositoryStorageMove } + let(:bulk_worker_klass) { ::SnippetScheduleBulkRepositoryShardMovesWorker } + end +end diff --git a/spec/services/snippets/update_repository_storage_service_spec.rb b/spec/services/snippets/update_repository_storage_service_spec.rb new file mode 100644 index 00000000000..b2bcd620d76 --- /dev/null +++ b/spec/services/snippets/update_repository_storage_service_spec.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Snippets::UpdateRepositoryStorageService do + include Gitlab::ShellAdapter + + subject { described_class.new(repository_storage_move) } + + describe "#execute" do + let_it_be_with_reload(:snippet) { create(:snippet, :repository) } + let_it_be(:destination) { 'test_second_storage' } + let_it_be(:checksum) { snippet.repository.checksum } + + let(:repository_storage_move_state) { :scheduled } + let(:repository_storage_move) { create(:snippet_repository_storage_move, repository_storage_move_state, container: snippet, destination_storage_name: destination) } + let(:snippet_repository_double) { double(:repository) } + let(:original_snippet_repository_double) { double(:repository) } + + before do + allow(Gitlab.config.repositories.storages).to receive(:keys).and_return(%w[default test_second_storage]) + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with(destination).and_return(SecureRandom.uuid) + allow(Gitlab::Git::Repository).to receive(:new).and_call_original + allow(Gitlab::Git::Repository).to receive(:new) + .with(destination, snippet.repository.raw.relative_path, snippet.repository.gl_repository, snippet.repository.full_path) + .and_return(snippet_repository_double) + allow(Gitlab::Git::Repository).to receive(:new) + .with('default', snippet.repository.raw.relative_path, nil, nil) + .and_return(original_snippet_repository_double) + end + + context 'when the move succeeds' do + it 'moves the repository to the new storage and unmarks the repository as read only' do + old_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do + snippet.repository.path_to_repo + end + + expect(snippet_repository_double).to receive(:replicate) + .with(snippet.repository.raw) + expect(snippet_repository_double).to receive(:checksum) + .and_return(checksum) + expect(original_snippet_repository_double).to receive(:remove) + + result = subject.execute + snippet.reload + + expect(result).to be_success + expect(snippet).not_to be_repository_read_only + expect(snippet.repository_storage).to eq(destination) + expect(gitlab_shell.repository_exists?('default', old_path)).to be(false) + expect(snippet.snippet_repository.shard_name).to eq(destination) + end + end + + context 'when the filesystems are the same' do + let(:destination) { snippet.repository_storage } + + it 'bails out and does nothing' do + result = subject.execute + + expect(result).to be_error + expect(result.message).to match(/SameFilesystemError/) + end + end + + context 'when the move fails' do + it 'unmarks the repository as read-only without updating the repository storage' do + expect(snippet_repository_double).to receive(:replicate) + .with(snippet.repository.raw) + .and_raise(Gitlab::Git::CommandError) + + result = subject.execute + + expect(result).to be_error + expect(snippet).not_to be_repository_read_only + expect(snippet.repository_storage).to eq('default') + expect(repository_storage_move).to be_failed + end + end + + context 'when the cleanup fails' do + it 'sets the correct state' do + expect(snippet_repository_double).to receive(:replicate) + .with(snippet.repository.raw) + expect(snippet_repository_double).to receive(:checksum) + .and_return(checksum) + expect(original_snippet_repository_double).to receive(:remove) + .and_raise(Gitlab::Git::CommandError) + + result = subject.execute + + expect(result).to be_error + expect(repository_storage_move).to be_cleanup_failed + end + end + + context 'when the checksum does not match' do + it 'unmarks the repository as read-only without updating the repository storage' do + expect(snippet_repository_double).to receive(:replicate) + .with(snippet.repository.raw) + expect(snippet_repository_double).to receive(:checksum) + .and_return('not matching checksum') + + result = subject.execute + + expect(result).to be_error + expect(snippet).not_to be_repository_read_only + expect(snippet.repository_storage).to eq('default') + end + end + + context 'when the repository move is finished' do + let(:repository_storage_move_state) { :finished } + + it 'is idempotent' do + expect do + result = subject.execute + + expect(result).to be_success + end.not_to change(repository_storage_move, :state) + end + end + + context 'when the repository move is failed' do + let(:repository_storage_move_state) { :failed } + + it 'is idempotent' do + expect do + result = subject.execute + + expect(result).to be_success + end.not_to change(repository_storage_move, :state) + end + end + end +end diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 90325c564bc..83d233a8112 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -100,17 +100,18 @@ RSpec.describe TodoService do end describe 'Issues' do - let(:issue) { create(:issue, project: project, assignees: [john_doe], author: author, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") } - let(:addressed_issue) { create(:issue, project: project, assignees: [john_doe], author: author, description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") } + let(:issue) { create(:issue, project: project, author: author, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") } + let(:addressed_issue) { create(:issue, project: project, author: author, description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") } + let(:assigned_issue) { create(:issue, project: project, assignees: [john_doe]) } let(:unassigned_issue) { create(:issue, project: project, assignees: []) } let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignees: [assignee], description: mentions) } let(:addressed_confident_issue) { create(:issue, :confidential, project: project, author: author, assignees: [assignee], description: directly_addressed) } describe '#new_issue' do it 'creates a todo if assigned' do - service.new_issue(issue, author) + service.new_issue(assigned_issue, author) - should_create_todo(user: john_doe, target: issue, action: Todo::ASSIGNED) + should_create_todo(user: john_doe, target: assigned_issue, action: Todo::ASSIGNED) end it 'does not create a todo if unassigned' do @@ -130,7 +131,7 @@ RSpec.describe TodoService do should_create_todo(user: member, target: issue, action: Todo::MENTIONED) should_create_todo(user: guest, target: issue, action: Todo::MENTIONED) should_create_todo(user: author, target: issue, action: Todo::MENTIONED) - should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED) + should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED) end @@ -140,7 +141,7 @@ RSpec.describe TodoService do should_create_todo(user: member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED) should_create_todo(user: guest, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED) should_create_todo(user: author, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: john_doe, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED) + should_create_todo(user: john_doe, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED) should_not_create_todo(user: non_member, target: addressed_issue, action: Todo::DIRECTLY_ADDRESSED) end @@ -244,6 +245,8 @@ RSpec.describe TodoService do end it 'does not create a todo if user was already mentioned and todo is pending' do + stub_feature_flags(multiple_todos: false) + create(:todo, :mentioned, user: member, project: project, target: issue, author: author) expect { service.update_issue(issue, author, skip_users) }.not_to change(member.todos, :count) @@ -256,6 +259,8 @@ RSpec.describe TodoService do end it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do + stub_feature_flags(multiple_todos: false) + create(:todo, :directly_addressed, user: member, project: project, target: addressed_issue, author: author) expect { service.update_issue(addressed_issue, author, skip_users) }.not_to change(member.todos, :count) @@ -622,6 +627,26 @@ RSpec.describe TodoService do expect(service.todo_exist?(unassigned_issue, author)).to be_truthy end end + + context 'when multiple_todos are enabled' do + before do + stub_feature_flags(multiple_todos: true) + end + + it 'creates a todo even if user already has a pending todo' do + create(:todo, :mentioned, user: member, project: project, target: issue, author: author) + + expect { service.update_issue(issue, author) }.to change(member.todos, :count) + end + + it 'creates multiple todos if a user is assigned and mentioned in a new issue' do + assigned_issue.description = mentions + service.new_issue(assigned_issue, author) + + should_create_todo(user: john_doe, target: assigned_issue, action: Todo::ASSIGNED) + should_create_todo(user: john_doe, target: assigned_issue, action: Todo::MENTIONED) + end + end end describe '#reassigned_assignable' do @@ -664,154 +689,161 @@ RSpec.describe TodoService do end describe 'Merge Requests' do - let(:mr_assigned) { create(:merge_request, source_project: project, author: author, assignees: [john_doe], description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") } - let(:addressed_mr_assigned) { create(:merge_request, source_project: project, author: author, assignees: [john_doe], description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") } - let(:mr_unassigned) { create(:merge_request, source_project: project, author: author, assignees: []) } + let(:mentioned_mr) { create(:merge_request, source_project: project, author: author, description: "- [ ] Task 1\n- [ ] Task 2 #{mentions}") } + let(:addressed_mr) { create(:merge_request, source_project: project, author: author, description: "#{directly_addressed}\n- [ ] Task 1\n- [ ] Task 2") } + let(:assigned_mr) { create(:merge_request, source_project: project, author: author, assignees: [john_doe]) } + let(:unassigned_mr) { create(:merge_request, source_project: project, author: author, assignees: []) } describe '#new_merge_request' do it 'creates a pending todo if assigned' do - service.new_merge_request(mr_assigned, author) + service.new_merge_request(assigned_mr, author) - should_create_todo(user: john_doe, target: mr_assigned, action: Todo::ASSIGNED) + should_create_todo(user: john_doe, target: assigned_mr, action: Todo::ASSIGNED) end it 'does not create a todo if unassigned' do - should_not_create_any_todo { service.new_merge_request(mr_unassigned, author) } + should_not_create_any_todo { service.new_merge_request(unassigned_mr, author) } end - it 'does not create a todo if assignee is the current user' do - should_not_create_any_todo { service.new_merge_request(mr_unassigned, john_doe) } + it 'creates a todo if assignee is the current user' do + service.new_merge_request(assigned_mr, john_doe) + + should_create_todo(user: john_doe, target: assigned_mr, author: john_doe, action: Todo::ASSIGNED) end it 'creates a todo for each valid mentioned user' do - service.new_merge_request(mr_assigned, author) + service.new_merge_request(mentioned_mr, author) - should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) - should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) + should_create_todo(user: member, target: mentioned_mr, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mentioned_mr, action: Todo::MENTIONED) + should_create_todo(user: author, target: mentioned_mr, action: Todo::MENTIONED) + should_create_todo(user: john_doe, target: mentioned_mr, action: Todo::MENTIONED) + should_not_create_todo(user: non_member, target: mentioned_mr, action: Todo::MENTIONED) end it 'creates a todo for each valid user based on the type of mention' do - mr_assigned.update!(description: directly_addressed_and_mentioned) + mentioned_mr.update!(description: directly_addressed_and_mentioned) - service.new_merge_request(mr_assigned, author) + service.new_merge_request(mentioned_mr, author) - should_create_todo(user: member, target: mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED) + should_create_todo(user: member, target: mentioned_mr, action: Todo::DIRECTLY_ADDRESSED) + should_not_create_todo(user: admin, target: mentioned_mr, action: Todo::MENTIONED) end it 'creates a directly addressed todo for each valid addressed user' do - service.new_merge_request(addressed_mr_assigned, author) + service.new_merge_request(addressed_mr, author) - should_create_todo(user: member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_create_todo(user: author, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: john_doe, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: non_member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) + should_create_todo(user: member, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_not_create_todo(user: guest, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_create_todo(user: author, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_create_todo(user: john_doe, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_not_create_todo(user: non_member, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) end end describe '#update_merge_request' do it 'creates a todo for each valid mentioned user not included in skip_users' do - service.update_merge_request(mr_assigned, author, skip_users) + service.update_merge_request(mentioned_mr, author, skip_users) - should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) - should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) - should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: skipped, target: mr_assigned, action: Todo::MENTIONED) + should_create_todo(user: member, target: mentioned_mr, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mentioned_mr, action: Todo::MENTIONED) + should_create_todo(user: john_doe, target: mentioned_mr, action: Todo::MENTIONED) + should_create_todo(user: author, target: mentioned_mr, action: Todo::MENTIONED) + should_not_create_todo(user: non_member, target: mentioned_mr, action: Todo::MENTIONED) + should_not_create_todo(user: skipped, target: mentioned_mr, action: Todo::MENTIONED) end it 'creates a todo for each valid user not included in skip_users based on the type of mention' do - mr_assigned.update!(description: directly_addressed_and_mentioned) + mentioned_mr.update!(description: directly_addressed_and_mentioned) - service.update_merge_request(mr_assigned, author, skip_users) + service.update_merge_request(mentioned_mr, author, skip_users) - should_create_todo(user: member, target: mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: skipped, target: mr_assigned) + should_create_todo(user: member, target: mentioned_mr, action: Todo::DIRECTLY_ADDRESSED) + should_not_create_todo(user: admin, target: mentioned_mr, action: Todo::MENTIONED) + should_not_create_todo(user: skipped, target: mentioned_mr) end it 'creates a directly addressed todo for each valid addressed user not included in skip_users' do - service.update_merge_request(addressed_mr_assigned, author, skip_users) + service.update_merge_request(addressed_mr, author, skip_users) - should_create_todo(user: member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_create_todo(user: john_doe, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_create_todo(user: author, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: non_member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: skipped, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) + should_create_todo(user: member, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_not_create_todo(user: guest, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_create_todo(user: john_doe, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_create_todo(user: author, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_not_create_todo(user: non_member, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_not_create_todo(user: skipped, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) end it 'does not create a todo if user was already mentioned and todo is pending' do - create(:todo, :mentioned, user: member, project: project, target: mr_assigned, author: author) + stub_feature_flags(multiple_todos: false) + + create(:todo, :mentioned, user: member, project: project, target: mentioned_mr, author: author) - expect { service.update_merge_request(mr_assigned, author) }.not_to change(member.todos, :count) + expect { service.update_merge_request(mentioned_mr, author) }.not_to change(member.todos, :count) end it 'does not create a todo if user was already mentioned and todo is done' do - create(:todo, :mentioned, :done, user: skipped, project: project, target: mr_assigned, author: author) + create(:todo, :mentioned, :done, user: skipped, project: project, target: mentioned_mr, author: author) - expect { service.update_merge_request(mr_assigned, author, skip_users) }.not_to change(skipped.todos, :count) + expect { service.update_merge_request(mentioned_mr, author, skip_users) }.not_to change(skipped.todos, :count) end it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do - create(:todo, :directly_addressed, user: member, project: project, target: addressed_mr_assigned, author: author) + stub_feature_flags(multiple_todos: false) + + create(:todo, :directly_addressed, user: member, project: project, target: addressed_mr, author: author) - expect { service.update_merge_request(addressed_mr_assigned, author) }.not_to change(member.todos, :count) + expect { service.update_merge_request(addressed_mr, author) }.not_to change(member.todos, :count) end it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is done' do - create(:todo, :directly_addressed, user: skipped, project: project, target: addressed_mr_assigned, author: author) + create(:todo, :directly_addressed, user: skipped, project: project, target: addressed_mr, author: author) - expect { service.update_merge_request(addressed_mr_assigned, author, skip_users) }.not_to change(skipped.todos, :count) + expect { service.update_merge_request(addressed_mr, author, skip_users) }.not_to change(skipped.todos, :count) end context 'with a task list' do it 'does not create todo when tasks are marked as completed' do - mr_assigned.update!(description: "- [x] Task 1\n- [X] Task 2 #{mentions}") + mentioned_mr.update!(description: "- [x] Task 1\n- [X] Task 2 #{mentions}") - service.update_merge_request(mr_assigned, author) + service.update_merge_request(mentioned_mr, author) - should_not_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: assignee, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) - should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: admin, target: mentioned_mr, action: Todo::MENTIONED) + should_not_create_todo(user: assignee, target: mentioned_mr, action: Todo::MENTIONED) + should_not_create_todo(user: author, target: mentioned_mr, action: Todo::MENTIONED) + should_not_create_todo(user: john_doe, target: mentioned_mr, action: Todo::MENTIONED) + should_not_create_todo(user: member, target: mentioned_mr, action: Todo::MENTIONED) + should_not_create_todo(user: non_member, target: mentioned_mr, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mentioned_mr, action: Todo::MENTIONED) end it 'does not create directly addressed todo when tasks are marked as completed' do - addressed_mr_assigned.update!(description: "#{directly_addressed}\n- [x] Task 1\n- [X] Task 2") + addressed_mr.update!(description: "#{directly_addressed}\n- [x] Task 1\n- [X] Task 2") - service.update_merge_request(addressed_mr_assigned, author) + service.update_merge_request(addressed_mr, author) - should_not_create_todo(user: admin, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: assignee, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: author, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: john_doe, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: non_member, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) - should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) + should_not_create_todo(user: admin, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_not_create_todo(user: assignee, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_not_create_todo(user: author, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_not_create_todo(user: john_doe, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_not_create_todo(user: member, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_not_create_todo(user: non_member, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) + should_not_create_todo(user: guest, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) end it 'does not raise an error when description not change' do - mr_assigned.update!(title: 'Sample') + mentioned_mr.update!(title: 'Sample') - expect { service.update_merge_request(mr_assigned, author) }.not_to raise_error + expect { service.update_merge_request(mentioned_mr, author) }.not_to raise_error end end end describe '#close_merge_request' do it 'marks related pending todos to the target for the user as done' do - first_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author) - second_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author) - service.close_merge_request(mr_assigned, john_doe) + first_todo = create(:todo, :assigned, user: john_doe, project: project, target: mentioned_mr, author: author) + second_todo = create(:todo, :assigned, user: john_doe, project: project, target: mentioned_mr, author: author) + service.close_merge_request(mentioned_mr, john_doe) expect(first_todo.reload).to be_done expect(second_todo.reload).to be_done @@ -820,55 +852,55 @@ RSpec.describe TodoService do describe '#merge_merge_request' do it 'marks related pending todos to the target for the user as done' do - first_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author) - second_todo = create(:todo, :assigned, user: john_doe, project: project, target: mr_assigned, author: author) - service.merge_merge_request(mr_assigned, john_doe) + first_todo = create(:todo, :assigned, user: john_doe, project: project, target: mentioned_mr, author: author) + second_todo = create(:todo, :assigned, user: john_doe, project: project, target: mentioned_mr, author: author) + service.merge_merge_request(mentioned_mr, john_doe) expect(first_todo.reload).to be_done expect(second_todo.reload).to be_done end it 'does not create todo for guests' do - service.merge_merge_request(mr_assigned, john_doe) - should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + service.merge_merge_request(mentioned_mr, john_doe) + should_not_create_todo(user: guest, target: mentioned_mr, action: Todo::MENTIONED) end it 'does not create directly addressed todo for guests' do - service.merge_merge_request(addressed_mr_assigned, john_doe) - should_not_create_todo(user: guest, target: addressed_mr_assigned, action: Todo::DIRECTLY_ADDRESSED) + service.merge_merge_request(addressed_mr, john_doe) + should_not_create_todo(user: guest, target: addressed_mr, action: Todo::DIRECTLY_ADDRESSED) end end describe '#new_award_emoji' do it 'marks related pending todos to the target for the user as done' do - todo = create(:todo, user: john_doe, project: project, target: mr_assigned, author: author) - service.new_award_emoji(mr_assigned, john_doe) + todo = create(:todo, user: john_doe, project: project, target: mentioned_mr, author: author) + service.new_award_emoji(mentioned_mr, john_doe) expect(todo.reload).to be_done end end describe '#merge_request_build_failed' do - let(:merge_participants) { [mr_unassigned.author, admin] } + let(:merge_participants) { [unassigned_mr.author, admin] } before do - allow(mr_unassigned).to receive(:merge_participants).and_return(merge_participants) + allow(unassigned_mr).to receive(:merge_participants).and_return(merge_participants) end it 'creates a pending todo for each merge_participant' do - service.merge_request_build_failed(mr_unassigned) + service.merge_request_build_failed(unassigned_mr) merge_participants.each do |participant| - should_create_todo(user: participant, author: participant, target: mr_unassigned, action: Todo::BUILD_FAILED) + should_create_todo(user: participant, author: participant, target: unassigned_mr, action: Todo::BUILD_FAILED) end end end describe '#merge_request_push' do it 'marks related pending todos to the target for the user as done' do - first_todo = create(:todo, :build_failed, user: author, project: project, target: mr_assigned, author: john_doe) - second_todo = create(:todo, :build_failed, user: john_doe, project: project, target: mr_assigned, author: john_doe) - service.merge_request_push(mr_assigned, author) + first_todo = create(:todo, :build_failed, user: author, project: project, target: mentioned_mr, author: john_doe) + second_todo = create(:todo, :build_failed, user: john_doe, project: project, target: mentioned_mr, author: john_doe) + service.merge_request_push(mentioned_mr, author) expect(first_todo.reload).to be_done expect(second_todo.reload).not_to be_done @@ -879,24 +911,24 @@ RSpec.describe TodoService do let(:merge_participants) { [admin, create(:user)] } before do - allow(mr_unassigned).to receive(:merge_participants).and_return(merge_participants) + allow(unassigned_mr).to receive(:merge_participants).and_return(merge_participants) end it 'creates a pending todo for each merge_participant' do - mr_unassigned.update!(merge_when_pipeline_succeeds: true, merge_user: admin) - service.merge_request_became_unmergeable(mr_unassigned) + unassigned_mr.update!(merge_when_pipeline_succeeds: true, merge_user: admin) + service.merge_request_became_unmergeable(unassigned_mr) merge_participants.each do |participant| - should_create_todo(user: participant, author: participant, target: mr_unassigned, action: Todo::UNMERGEABLE) + should_create_todo(user: participant, author: participant, target: unassigned_mr, action: Todo::UNMERGEABLE) end end end describe '#mark_todo' do it 'creates a todo from a merge request' do - service.mark_todo(mr_unassigned, author) + service.mark_todo(unassigned_mr, author) - should_create_todo(user: author, target: mr_unassigned, action: Todo::MARKED) + should_create_todo(user: author, target: unassigned_mr, action: Todo::MARKED) end end @@ -913,33 +945,33 @@ RSpec.describe TodoService do end let(:mention) { john_doe.to_reference } - let(:diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") } - let(:addressed_diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "#{mention}, hey!") } - let(:legacy_diff_note_on_merge_request) { create(:legacy_diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") } + let(:diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: unassigned_mr, author: author, note: "Hey #{mention}") } + let(:addressed_diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: unassigned_mr, author: author, note: "#{mention}, hey!") } + let(:legacy_diff_note_on_merge_request) { create(:legacy_diff_note_on_merge_request, project: project, noteable: unassigned_mr, author: author, note: "Hey #{mention}") } it 'creates a todo for mentioned user on new diff note' do service.new_note(diff_note_on_merge_request, author) - should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: diff_note_on_merge_request) + should_create_todo(user: john_doe, target: unassigned_mr, author: author, action: Todo::MENTIONED, note: diff_note_on_merge_request) end it 'creates a directly addressed todo for addressed user on new diff note' do service.new_note(addressed_diff_note_on_merge_request, author) - should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::DIRECTLY_ADDRESSED, note: addressed_diff_note_on_merge_request) + should_create_todo(user: john_doe, target: unassigned_mr, author: author, action: Todo::DIRECTLY_ADDRESSED, note: addressed_diff_note_on_merge_request) end it 'creates a todo for mentioned user on legacy diff note' do service.new_note(legacy_diff_note_on_merge_request, author) - should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: legacy_diff_note_on_merge_request) + should_create_todo(user: john_doe, target: unassigned_mr, author: author, action: Todo::MENTIONED, note: legacy_diff_note_on_merge_request) end it 'does not create todo for guests' do - note_on_merge_request = create :note_on_merge_request, project: project, noteable: mr_assigned, note: mentions + note_on_merge_request = create :note_on_merge_request, project: project, noteable: mentioned_mr, note: mentions service.new_note(note_on_merge_request, author) - should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mentioned_mr, action: Todo::MENTIONED) end end end @@ -1013,6 +1045,8 @@ RSpec.describe TodoService do end it 'does not create a todo if user was already mentioned and todo is pending' do + stub_feature_flags(multiple_todos: false) + create(:todo, :mentioned, user: member, project: project, target: noteable, author: author) expect { service.update_note(note, author, skip_users) }.not_to change(member.todos, :count) @@ -1025,6 +1059,8 @@ RSpec.describe TodoService do end it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do + stub_feature_flags(multiple_todos: false) + create(:todo, :directly_addressed, user: member, project: project, target: noteable, author: author) expect { service.update_note(addressed_note, author, skip_users) }.not_to change(member.todos, :count) @@ -1038,7 +1074,7 @@ RSpec.describe TodoService do end it 'updates cached counts when a todo is created' do - issue = create(:issue, project: project, assignees: [john_doe], author: author, description: mentions) + issue = create(:issue, project: project, assignees: [john_doe], author: author) expect(john_doe.todos_pending_count).to eq(0) expect(john_doe).to receive(:update_todos_count_cache).and_call_original diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb index 274c44394f3..b30b7e6eb56 100644 --- a/spec/services/users/update_service_spec.rb +++ b/spec/services/users/update_service_spec.rb @@ -31,7 +31,7 @@ RSpec.describe Users::UpdateService do result = update_user(user, { username: 'taken' }) end.not_to change { user.reload.username } expect(result[:status]).to eq(:error) - expect(result[:message]).to eq('Username has already been taken') + expect(result[:message]).to eq('A user, alias, or group already exists with that username.') end it 'updates the status if status params were given' do diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb index a607a6734b0..2fe72ab31c2 100644 --- a/spec/services/web_hook_service_spec.rb +++ b/spec/services/web_hook_service_spec.rb @@ -131,6 +131,15 @@ RSpec.describe WebHookService do end end + context 'when url is not encoded' do + let(:project_hook) { create(:project_hook, url: 'http://server.com/my path/') } + + it 'handles exceptions' do + expect(service_instance.execute).to eq(status: :error, message: 'bad URI(is not URI?): "http://server.com/my path/"') + expect { service_instance.execute }.not_to raise_error + end + end + context 'when request body size is too big' do it 'does not perform the request' do stub_const("#{described_class}::REQUEST_BODY_SIZE_LIMIT", 10.bytes) |