summaryrefslogtreecommitdiff
path: root/spec/workers
diff options
context:
space:
mode:
Diffstat (limited to 'spec/workers')
-rw-r--r--spec/workers/analytics/usage_trends/counter_job_worker_spec.rb3
-rw-r--r--spec/workers/ci/ref_delete_unlock_artifacts_worker_spec.rb34
-rw-r--r--spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb4
-rw-r--r--spec/workers/clusters/integrations/check_prometheus_health_worker_spec.rb (renamed from spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb)8
-rw-r--r--spec/workers/concerns/application_worker_spec.rb381
-rw-r--r--spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb5
-rw-r--r--spec/workers/database/drop_detached_partitions_worker_spec.rb8
-rw-r--r--spec/workers/database/partition_management_worker_spec.rb9
-rw-r--r--spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb4
-rw-r--r--spec/workers/deployments/archive_in_project_worker_spec.rb18
-rw-r--r--spec/workers/email_receiver_worker_spec.rb9
-rw-r--r--spec/workers/emails_on_push_worker_spec.rb37
-rw-r--r--spec/workers/every_sidekiq_worker_spec.rb2
-rw-r--r--spec/workers/integrations/create_external_cross_reference_worker_spec.rb128
-rw-r--r--spec/workers/issue_rebalancing_worker_spec.rb16
-rw-r--r--spec/workers/issues/placement_worker_spec.rb151
-rw-r--r--spec/workers/issues/rebalancing_worker_spec.rb90
-rw-r--r--spec/workers/issues/reschedule_stuck_issue_rebalances_worker_spec.rb26
-rw-r--r--spec/workers/loose_foreign_keys/cleanup_worker_spec.rb153
-rw-r--r--spec/workers/namespaces/invite_team_email_worker_spec.rb27
-rw-r--r--spec/workers/packages/maven/metadata/sync_worker_spec.rb3
-rw-r--r--spec/workers/post_receive_spec.rb8
-rw-r--r--spec/workers/propagate_integration_group_worker_spec.rb2
-rw-r--r--spec/workers/propagate_integration_inherit_descendant_worker_spec.rb4
-rw-r--r--spec/workers/propagate_integration_project_worker_spec.rb2
-rw-r--r--spec/workers/ssh_keys/expired_notification_worker_spec.rb6
-rw-r--r--spec/workers/tasks_to_be_done/create_worker_spec.rb36
-rw-r--r--spec/workers/users/deactivate_dormant_users_worker_spec.rb36
28 files changed, 1121 insertions, 89 deletions
diff --git a/spec/workers/analytics/usage_trends/counter_job_worker_spec.rb b/spec/workers/analytics/usage_trends/counter_job_worker_spec.rb
index dd180229d12..c45ec20fe5a 100644
--- a/spec/workers/analytics/usage_trends/counter_job_worker_spec.rb
+++ b/spec/workers/analytics/usage_trends/counter_job_worker_spec.rb
@@ -11,7 +11,8 @@ RSpec.describe Analytics::UsageTrends::CounterJobWorker do
let(:job_args) { [users_measurement_identifier, user_1.id, user_2.id, recorded_at] }
before do
- allow(::Analytics::UsageTrends::Measurement.connection).to receive(:transaction_open?).and_return(false)
+ allow(::ApplicationRecord.connection).to receive(:transaction_open?).and_return(false)
+ allow(::Ci::ApplicationRecord.connection).to receive(:transaction_open?).and_return(false) if ::Ci::ApplicationRecord.connection_class?
end
include_examples 'an idempotent worker' do
diff --git a/spec/workers/ci/ref_delete_unlock_artifacts_worker_spec.rb b/spec/workers/ci/ref_delete_unlock_artifacts_worker_spec.rb
index f510852e753..fe4bc2421a4 100644
--- a/spec/workers/ci/ref_delete_unlock_artifacts_worker_spec.rb
+++ b/spec/workers/ci/ref_delete_unlock_artifacts_worker_spec.rb
@@ -4,7 +4,9 @@ require 'spec_helper'
RSpec.describe Ci::RefDeleteUnlockArtifactsWorker do
describe '#perform' do
- subject(:perform) { described_class.new.perform(project_id, user_id, ref) }
+ subject(:perform) { worker.perform(project_id, user_id, ref) }
+
+ let(:worker) { described_class.new }
let(:ref) { 'refs/heads/master' }
@@ -40,6 +42,36 @@ RSpec.describe Ci::RefDeleteUnlockArtifactsWorker do
expect(service).to have_received(:execute).with(ci_ref)
end
+
+ context 'when a locked pipeline with persisted artifacts exists' do
+ let!(:pipeline) { create(:ci_pipeline, :with_persisted_artifacts, ref: 'master', project: project, locked: :artifacts_locked) }
+
+ context 'with ci_update_unlocked_job_artifacts disabled' do
+ before do
+ stub_feature_flags(ci_update_unlocked_job_artifacts: false)
+ end
+
+ it 'logs the correct extra metadata' do
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:unlocked_pipelines, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:unlocked_job_artifacts, 0)
+
+ perform
+ end
+ end
+
+ context 'with ci_update_unlocked_job_artifacts enabled' do
+ before do
+ stub_feature_flags(ci_update_unlocked_job_artifacts: true)
+ end
+
+ it 'logs the correct extra metadata' do
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:unlocked_pipelines, 1)
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:unlocked_job_artifacts, 2)
+
+ perform
+ end
+ end
+ end
end
context 'when ci ref does not exist for the given project' do
diff --git a/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb b/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
index 650be1e84a9..be7f7ef5c8c 100644
--- a/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
+++ b/spec/workers/ci/resource_groups/assign_resource_from_resource_group_worker_spec.rb
@@ -9,6 +9,10 @@ RSpec.describe Ci::ResourceGroups::AssignResourceFromResourceGroupWorker do
expect(described_class.get_deduplicate_strategy).to eq(:until_executed)
end
+ it 'has an option to reschedule once if deduplicated' do
+ expect(described_class.get_deduplication_options).to include({ if_deduplicated: :reschedule_once })
+ end
+
describe '#perform' do
subject { worker.perform(resource_group_id) }
diff --git a/spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb b/spec/workers/clusters/integrations/check_prometheus_health_worker_spec.rb
index fb779bf3b01..6f70870bd09 100644
--- a/spec/workers/clusters/applications/check_prometheus_health_worker_spec.rb
+++ b/spec/workers/clusters/integrations/check_prometheus_health_worker_spec.rb
@@ -2,16 +2,16 @@
require 'spec_helper'
-RSpec.describe Clusters::Applications::CheckPrometheusHealthWorker, '#perform' do
+RSpec.describe Clusters::Integrations::CheckPrometheusHealthWorker, '#perform' do
subject { described_class.new.perform }
it 'triggers health service' do
cluster = create(:cluster)
allow(Gitlab::Monitor::DemoProjects).to receive(:primary_keys)
- allow(Clusters::Cluster).to receive_message_chain(:with_application_prometheus, :with_project_http_integrations).and_return([cluster])
+ allow(Clusters::Cluster).to receive_message_chain(:with_integration_prometheus, :with_project_http_integrations).and_return([cluster])
- service_instance = instance_double(Clusters::Applications::PrometheusHealthCheckService)
- expect(Clusters::Applications::PrometheusHealthCheckService).to receive(:new).with(cluster).and_return(service_instance)
+ service_instance = instance_double(Clusters::Integrations::PrometheusHealthCheckService)
+ expect(Clusters::Integrations::PrometheusHealthCheckService).to receive(:new).with(cluster).and_return(service_instance)
expect(service_instance).to receive(:execute)
subject
diff --git a/spec/workers/concerns/application_worker_spec.rb b/spec/workers/concerns/application_worker_spec.rb
index af038c81b9e..fbf39b3c7cd 100644
--- a/spec/workers/concerns/application_worker_spec.rb
+++ b/spec/workers/concerns/application_worker_spec.rb
@@ -285,48 +285,38 @@ RSpec.describe ApplicationWorker do
end
end
- describe '.bulk_perform_async' do
- before do
- stub_const(worker.name, worker)
+ context 'different kinds of push_bulk' do
+ shared_context 'disable the `sidekiq_push_bulk_in_batches` feature flag' do
+ before do
+ stub_feature_flags(sidekiq_push_bulk_in_batches: false)
+ end
end
- it 'enqueues jobs in bulk' do
- Sidekiq::Testing.fake! do
- worker.bulk_perform_async([['Foo', [1]], ['Foo', [2]]])
-
- expect(worker.jobs.count).to eq 2
- expect(worker.jobs).to all(include('enqueued_at'))
+ shared_context 'set safe limit beyond the number of jobs to be enqueued' do
+ before do
+ stub_const("#{described_class}::SAFE_PUSH_BULK_LIMIT", args.count + 1)
end
end
- end
- describe '.bulk_perform_in' do
- before do
- stub_const(worker.name, worker)
+ shared_context 'set safe limit below the number of jobs to be enqueued' do
+ before do
+ stub_const("#{described_class}::SAFE_PUSH_BULK_LIMIT", 2)
+ end
end
- context 'when delay is valid' do
- it 'correctly schedules jobs' do
- Sidekiq::Testing.fake! do
- worker.bulk_perform_in(1.minute, [['Foo', [1]], ['Foo', [2]]])
+ shared_examples_for 'returns job_id of all enqueued jobs' do
+ let(:job_id_regex) { /[0-9a-f]{12}/ }
- expect(worker.jobs.count).to eq 2
- expect(worker.jobs).to all(include('at'))
- end
- end
- end
+ it 'returns job_id of all enqueued jobs' do
+ job_ids = perform_action
- context 'when delay is invalid' do
- it 'raises an ArgumentError exception' do
- expect { worker.bulk_perform_in(-60, [['Foo']]) }
- .to raise_error(ArgumentError)
+ expect(job_ids.count).to eq(args.count)
+ expect(job_ids).to all(match(job_id_regex))
end
end
- context 'with batches' do
- let(:batch_delay) { 1.minute }
-
- it 'correctly schedules jobs' do
+ shared_examples_for 'enqueues the jobs in a batched fashion, with each batch enqueing jobs as per the set safe limit' do
+ it 'enqueues the jobs in a batched fashion, with each batch enqueing jobs as per the set safe limit' do
expect(Sidekiq::Client).to(
receive(:push_bulk).with(hash_including('args' => [['Foo', [1]], ['Foo', [2]]]))
.ordered
@@ -337,29 +327,318 @@ RSpec.describe ApplicationWorker do
.and_call_original)
expect(Sidekiq::Client).to(
receive(:push_bulk).with(hash_including('args' => [['Foo', [5]]]))
- .ordered
- .and_call_original)
+ .ordered
+ .and_call_original)
- worker.bulk_perform_in(
- 1.minute,
- [['Foo', [1]], ['Foo', [2]], ['Foo', [3]], ['Foo', [4]], ['Foo', [5]]],
- batch_size: 2, batch_delay: batch_delay)
-
- expect(worker.jobs.count).to eq 5
- expect(worker.jobs[0]['at']).to eq(worker.jobs[1]['at'])
- expect(worker.jobs[2]['at']).to eq(worker.jobs[3]['at'])
- expect(worker.jobs[2]['at'] - worker.jobs[1]['at']).to eq(batch_delay)
- expect(worker.jobs[4]['at'] - worker.jobs[3]['at']).to eq(batch_delay)
- end
-
- context 'when batch_size is invalid' do
- it 'raises an ArgumentError exception' do
- expect do
- worker.bulk_perform_in(1.minute,
- [['Foo']],
- batch_size: -1, batch_delay: batch_delay)
- end.to raise_error(ArgumentError)
+ perform_action
+
+ expect(worker.jobs.count).to eq args.count
+ expect(worker.jobs).to all(include('enqueued_at'))
+ end
+ end
+
+ shared_examples_for 'enqueues jobs in one go' do
+ it 'enqueues jobs in one go' do
+ expect(Sidekiq::Client).to(
+ receive(:push_bulk).with(hash_including('args' => args)).once.and_call_original)
+ expect(Sidekiq.logger).not_to receive(:info)
+
+ perform_action
+
+ expect(worker.jobs.count).to eq args.count
+ expect(worker.jobs).to all(include('enqueued_at'))
+ end
+ end
+
+ shared_examples_for 'logs bulk insertions' do
+ it 'logs arguments and job IDs' do
+ worker.log_bulk_perform_async!
+
+ expect(Sidekiq.logger).to(
+ receive(:info).with(hash_including('class' => worker.name, 'args_list' => args)).once.and_call_original)
+ expect(Sidekiq.logger).to(
+ receive(:info).with(hash_including('class' => worker.name, 'jid_list' => anything)).once.and_call_original)
+
+ perform_action
+ end
+ end
+
+ before do
+ stub_const(worker.name, worker)
+ end
+
+ let(:args) do
+ [
+ ['Foo', [1]],
+ ['Foo', [2]],
+ ['Foo', [3]],
+ ['Foo', [4]],
+ ['Foo', [5]]
+ ]
+ end
+
+ describe '.bulk_perform_async' do
+ shared_examples_for 'does not schedule the jobs for any specific time' do
+ it 'does not schedule the jobs for any specific time' do
+ perform_action
+
+ expect(worker.jobs).to all(exclude('at'))
+ end
+ end
+
+ subject(:perform_action) do
+ worker.bulk_perform_async(args)
+ end
+
+ context 'push_bulk in safe limit batches' do
+ context 'when the number of jobs to be enqueued does not exceed the safe limit' do
+ include_context 'set safe limit beyond the number of jobs to be enqueued'
+
+ it_behaves_like 'enqueues jobs in one go'
+ it_behaves_like 'logs bulk insertions'
+ it_behaves_like 'returns job_id of all enqueued jobs'
+ it_behaves_like 'does not schedule the jobs for any specific time'
end
+
+ context 'when the number of jobs to be enqueued exceeds safe limit' do
+ include_context 'set safe limit below the number of jobs to be enqueued'
+
+ it_behaves_like 'enqueues the jobs in a batched fashion, with each batch enqueing jobs as per the set safe limit'
+ it_behaves_like 'returns job_id of all enqueued jobs'
+ it_behaves_like 'does not schedule the jobs for any specific time'
+ end
+
+ context 'when the feature flag `sidekiq_push_bulk_in_batches` is disabled' do
+ include_context 'disable the `sidekiq_push_bulk_in_batches` feature flag'
+
+ context 'when the number of jobs to be enqueued does not exceed the safe limit' do
+ include_context 'set safe limit beyond the number of jobs to be enqueued'
+
+ it_behaves_like 'enqueues jobs in one go'
+ it_behaves_like 'logs bulk insertions'
+ it_behaves_like 'returns job_id of all enqueued jobs'
+ it_behaves_like 'does not schedule the jobs for any specific time'
+ end
+
+ context 'when the number of jobs to be enqueued exceeds safe limit' do
+ include_context 'set safe limit below the number of jobs to be enqueued'
+
+ it_behaves_like 'enqueues jobs in one go'
+ it_behaves_like 'returns job_id of all enqueued jobs'
+ it_behaves_like 'does not schedule the jobs for any specific time'
+ end
+ end
+ end
+ end
+
+ describe '.bulk_perform_in' do
+ context 'without batches' do
+ shared_examples_for 'schedules all the jobs at a specific time' do
+ it 'schedules all the jobs at a specific time' do
+ perform_action
+
+ worker.jobs.each do |job_detail|
+ expect(job_detail['at']).to be_within(3.seconds).of(expected_scheduled_at_time)
+ end
+ end
+ end
+
+ let(:delay) { 3.minutes }
+ let(:expected_scheduled_at_time) { Time.current.to_i + delay.to_i }
+
+ subject(:perform_action) do
+ worker.bulk_perform_in(delay, args)
+ end
+
+ context 'when the scheduled time falls in the past' do
+ let(:delay) { -60 }
+
+ it 'raises an ArgumentError exception' do
+ expect { perform_action }
+ .to raise_error(ArgumentError)
+ end
+ end
+
+ context 'push_bulk in safe limit batches' do
+ context 'when the number of jobs to be enqueued does not exceed the safe limit' do
+ include_context 'set safe limit beyond the number of jobs to be enqueued'
+
+ it_behaves_like 'enqueues jobs in one go'
+ it_behaves_like 'returns job_id of all enqueued jobs'
+ it_behaves_like 'schedules all the jobs at a specific time'
+ end
+
+ context 'when the number of jobs to be enqueued exceeds safe limit' do
+ include_context 'set safe limit below the number of jobs to be enqueued'
+
+ it_behaves_like 'enqueues the jobs in a batched fashion, with each batch enqueing jobs as per the set safe limit'
+ it_behaves_like 'returns job_id of all enqueued jobs'
+ it_behaves_like 'schedules all the jobs at a specific time'
+ end
+
+ context 'when the feature flag `sidekiq_push_bulk_in_batches` is disabled' do
+ include_context 'disable the `sidekiq_push_bulk_in_batches` feature flag'
+
+ context 'when the number of jobs to be enqueued does not exceed the safe limit' do
+ include_context 'set safe limit beyond the number of jobs to be enqueued'
+
+ it_behaves_like 'enqueues jobs in one go'
+ it_behaves_like 'returns job_id of all enqueued jobs'
+ it_behaves_like 'schedules all the jobs at a specific time'
+ end
+
+ context 'when the number of jobs to be enqueued exceeds safe limit' do
+ include_context 'set safe limit below the number of jobs to be enqueued'
+
+ it_behaves_like 'enqueues jobs in one go'
+ it_behaves_like 'returns job_id of all enqueued jobs'
+ it_behaves_like 'schedules all the jobs at a specific time'
+ end
+ end
+ end
+ end
+
+ context 'with batches' do
+ shared_examples_for 'schedules all the jobs at a specific time, per batch' do
+ it 'schedules all the jobs at a specific time, per batch' do
+ perform_action
+
+ expect(worker.jobs[0]['at']).to eq(worker.jobs[1]['at'])
+ expect(worker.jobs[2]['at']).to eq(worker.jobs[3]['at'])
+ expect(worker.jobs[2]['at'] - worker.jobs[1]['at']).to eq(batch_delay)
+ expect(worker.jobs[4]['at'] - worker.jobs[3]['at']).to eq(batch_delay)
+ end
+ end
+
+ let(:delay) { 1.minute }
+ let(:batch_size) { 2 }
+ let(:batch_delay) { 10.minutes }
+
+ subject(:perform_action) do
+ worker.bulk_perform_in(delay, args, batch_size: batch_size, batch_delay: batch_delay)
+ end
+
+ context 'when the `batch_size` is invalid' do
+ context 'when `batch_size` is 0' do
+ let(:batch_size) { 0 }
+
+ it 'raises an ArgumentError exception' do
+ expect { perform_action }
+ .to raise_error(ArgumentError)
+ end
+ end
+
+ context 'when `batch_size` is negative' do
+ let(:batch_size) { -3 }
+
+ it 'raises an ArgumentError exception' do
+ expect { perform_action }
+ .to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ context 'when the `batch_delay` is invalid' do
+ context 'when `batch_delay` is 0' do
+ let(:batch_delay) { 0.minutes }
+
+ it 'raises an ArgumentError exception' do
+ expect { perform_action }
+ .to raise_error(ArgumentError)
+ end
+ end
+
+ context 'when `batch_delay` is negative' do
+ let(:batch_delay) { -3.minutes }
+
+ it 'raises an ArgumentError exception' do
+ expect { perform_action }
+ .to raise_error(ArgumentError)
+ end
+ end
+ end
+
+ context 'push_bulk in safe limit batches' do
+ context 'when the number of jobs to be enqueued does not exceed the safe limit' do
+ include_context 'set safe limit beyond the number of jobs to be enqueued'
+
+ it_behaves_like 'enqueues jobs in one go'
+ it_behaves_like 'returns job_id of all enqueued jobs'
+ it_behaves_like 'schedules all the jobs at a specific time, per batch'
+ end
+
+ context 'when the number of jobs to be enqueued exceeds safe limit' do
+ include_context 'set safe limit below the number of jobs to be enqueued'
+
+ it_behaves_like 'enqueues the jobs in a batched fashion, with each batch enqueing jobs as per the set safe limit'
+ it_behaves_like 'returns job_id of all enqueued jobs'
+ it_behaves_like 'schedules all the jobs at a specific time, per batch'
+ end
+
+ context 'when the feature flag `sidekiq_push_bulk_in_batches` is disabled' do
+ include_context 'disable the `sidekiq_push_bulk_in_batches` feature flag'
+
+ context 'when the number of jobs to be enqueued does not exceed the safe limit' do
+ include_context 'set safe limit beyond the number of jobs to be enqueued'
+
+ it_behaves_like 'enqueues jobs in one go'
+ it_behaves_like 'returns job_id of all enqueued jobs'
+ it_behaves_like 'schedules all the jobs at a specific time, per batch'
+ end
+
+ context 'when the number of jobs to be enqueued exceeds safe limit' do
+ include_context 'set safe limit below the number of jobs to be enqueued'
+
+ it_behaves_like 'enqueues jobs in one go'
+ it_behaves_like 'returns job_id of all enqueued jobs'
+ it_behaves_like 'schedules all the jobs at a specific time, per batch'
+ end
+ end
+ end
+ end
+ end
+ end
+
+ describe '.with_status' do
+ around do |example|
+ Sidekiq::Testing.fake!(&example)
+ end
+
+ context 'when the worker does have status_expiration set' do
+ let(:status_expiration_worker) do
+ Class.new(worker) do
+ sidekiq_options status_expiration: 3
+ end
+ end
+
+ it 'uses status_expiration from the worker' do
+ status_expiration_worker.with_status.perform_async
+
+ expect(Sidekiq::Queues[status_expiration_worker.queue].first).to include('status_expiration' => 3)
+ expect(Sidekiq::Queues[status_expiration_worker.queue].length).to eq(1)
+ end
+
+ it 'uses status_expiration from the worker without with_status' do
+ status_expiration_worker.perform_async
+
+ expect(Sidekiq::Queues[status_expiration_worker.queue].first).to include('status_expiration' => 3)
+ expect(Sidekiq::Queues[status_expiration_worker.queue].length).to eq(1)
+ end
+ end
+
+ context 'when the worker does not have status_expiration set' do
+ it 'uses the default status_expiration' do
+ worker.with_status.perform_async
+
+ expect(Sidekiq::Queues[worker.queue].first).to include('status_expiration' => Gitlab::SidekiqStatus::DEFAULT_EXPIRATION)
+ expect(Sidekiq::Queues[worker.queue].length).to eq(1)
+ end
+
+ it 'does not set status_expiration without with_status' do
+ worker.perform_async
+
+ expect(Sidekiq::Queues[worker.queue].first).not_to include('status_expiration')
+ expect(Sidekiq::Queues[worker.queue].length).to eq(1)
end
end
end
diff --git a/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb b/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb
index d4126fe688a..cbffb8f3870 100644
--- a/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb
+++ b/spec/workers/container_expiration_policies/cleanup_container_repository_worker_spec.rb
@@ -82,8 +82,9 @@ RSpec.describe ContainerExpirationPolicies::CleanupContainerRepositoryWorker do
nil | 10 | nil
0 | 5 | nil
10 | 0 | 0
- 10 | 5 | 0.5
- 3 | 10 | (10 / 3.to_f)
+ 10 | 5 | 50.0
+ 17 | 3 | 17.65
+ 3 | 10 | 333.33
end
with_them do
diff --git a/spec/workers/database/drop_detached_partitions_worker_spec.rb b/spec/workers/database/drop_detached_partitions_worker_spec.rb
index 8693878ddd5..a10fcaaa5d9 100644
--- a/spec/workers/database/drop_detached_partitions_worker_spec.rb
+++ b/spec/workers/database/drop_detached_partitions_worker_spec.rb
@@ -6,21 +6,19 @@ RSpec.describe Database::DropDetachedPartitionsWorker do
describe '#perform' do
subject { described_class.new.perform }
- let(:monitoring) { instance_double('PartitionMonitoring', report_metrics: nil) }
-
before do
allow(Gitlab::Database::Partitioning).to receive(:drop_detached_partitions)
- allow(Gitlab::Database::Partitioning::PartitionMonitoring).to receive(:new).and_return(monitoring)
+ allow(Gitlab::Database::Partitioning).to receive(:report_metrics)
end
- it 'delegates to Partitioning.drop_detached_partitions' do
+ it 'drops detached partitions' do
expect(Gitlab::Database::Partitioning).to receive(:drop_detached_partitions)
subject
end
it 'reports partition metrics' do
- expect(monitoring).to receive(:report_metrics)
+ expect(Gitlab::Database::Partitioning).to receive(:report_metrics)
subject
end
diff --git a/spec/workers/database/partition_management_worker_spec.rb b/spec/workers/database/partition_management_worker_spec.rb
index 9ded36743a8..e5362e95f48 100644
--- a/spec/workers/database/partition_management_worker_spec.rb
+++ b/spec/workers/database/partition_management_worker_spec.rb
@@ -6,20 +6,19 @@ RSpec.describe Database::PartitionManagementWorker do
describe '#perform' do
subject { described_class.new.perform }
- let(:monitoring) { instance_double('PartitionMonitoring', report_metrics: nil) }
-
before do
- allow(Gitlab::Database::Partitioning::PartitionMonitoring).to receive(:new).and_return(monitoring)
+ allow(Gitlab::Database::Partitioning).to receive(:sync_partitions)
+ allow(Gitlab::Database::Partitioning).to receive(:report_metrics)
end
- it 'delegates to Partitioning' do
+ it 'syncs partitions' do
expect(Gitlab::Database::Partitioning).to receive(:sync_partitions)
subject
end
it 'reports partition metrics' do
- expect(monitoring).to receive(:report_metrics)
+ expect(Gitlab::Database::Partitioning).to receive(:report_metrics)
subject
end
diff --git a/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb b/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb
index d3234f4c212..ae0cb097ebf 100644
--- a/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb
+++ b/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb
@@ -12,8 +12,8 @@ RSpec.describe DependencyProxy::ImageTtlGroupPolicyWorker do
subject { worker.perform }
context 'when there are images to expire' do
- let_it_be_with_reload(:old_blob) { create(:dependency_proxy_blob, group: group, updated_at: 1.year.ago) }
- let_it_be_with_reload(:old_manifest) { create(:dependency_proxy_manifest, group: group, updated_at: 1.year.ago) }
+ let_it_be_with_reload(:old_blob) { create(:dependency_proxy_blob, group: group, read_at: 1.year.ago) }
+ let_it_be_with_reload(:old_manifest) { create(:dependency_proxy_manifest, group: group, read_at: 1.year.ago) }
let_it_be_with_reload(:new_blob) { create(:dependency_proxy_blob, group: group) }
let_it_be_with_reload(:new_manifest) { create(:dependency_proxy_manifest, group: group) }
diff --git a/spec/workers/deployments/archive_in_project_worker_spec.rb b/spec/workers/deployments/archive_in_project_worker_spec.rb
new file mode 100644
index 00000000000..6435fe8bea1
--- /dev/null
+++ b/spec/workers/deployments/archive_in_project_worker_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Deployments::ArchiveInProjectWorker do
+ subject { described_class.new.perform(deployment&.project_id) }
+
+ describe '#perform' do
+ let(:deployment) { create(:deployment, :success) }
+
+ it 'executes Deployments::ArchiveInProjectService' do
+ expect(Deployments::ArchiveInProjectService)
+ .to receive(:new).with(deployment.project, nil).and_call_original
+
+ subject
+ end
+ end
+end
diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb
index 83e13ded7b3..83720ee132b 100644
--- a/spec/workers/email_receiver_worker_spec.rb
+++ b/spec/workers/email_receiver_worker_spec.rb
@@ -37,6 +37,15 @@ RSpec.describe EmailReceiverWorker, :mailer do
expect(email.to).to eq(["jake@adventuretime.ooo"])
expect(email.subject).to include("Rejected")
end
+
+ it 'strips out the body before passing to EmailRejectionMailer' do
+ mail = Mail.new(raw_message)
+ mail.body = nil
+
+ expect(EmailRejectionMailer).to receive(:rejection).with(anything, mail.encoded, anything).and_call_original
+
+ described_class.new.perform(raw_message)
+ end
end
context 'when the error is Gitlab::Email::AutoGeneratedEmailError' do
diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb
index 6c37c422aed..3e313610054 100644
--- a/spec/workers/emails_on_push_worker_spec.rb
+++ b/spec/workers/emails_on_push_worker_spec.rb
@@ -139,6 +139,43 @@ RSpec.describe EmailsOnPushWorker, :mailer do
perform
end
+
+ context 'when SMIME signing is enabled' do
+ include SmimeHelper
+
+ before :context do
+ @root_ca = generate_root
+ @cert = generate_cert(signer_ca: @root_ca)
+ end
+
+ let(:root_certificate) do
+ Gitlab::X509::Certificate.new(@root_ca[:key], @root_ca[:cert])
+ end
+
+ let(:certificate) do
+ Gitlab::X509::Certificate.new(@cert[:key], @cert[:cert])
+ end
+
+ before do
+ allow(Gitlab::Email::Hook::SmimeSignatureInterceptor).to receive(:certificate).and_return(certificate)
+
+ Mail.register_interceptor(Gitlab::Email::Hook::SmimeSignatureInterceptor)
+ end
+
+ after do
+ Mail.unregister_interceptor(Gitlab::Email::Hook::SmimeSignatureInterceptor)
+ end
+
+ it 'does not sign the email multiple times' do
+ perform
+
+ ActionMailer::Base.deliveries.each do |mail|
+ expect(mail.header['Content-Type'].value).to match('multipart/signed').and match('protocol="application/x-pkcs7-signature"')
+
+ expect(mail.to_s.scan(/Content-Disposition: attachment;\r\n filename=smime.p7s/).size).to eq(1)
+ end
+ end
+ end
end
context "when recipients are invalid" do
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index 9a4b27997e9..d00243672f9 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -316,6 +316,8 @@ RSpec.describe 'Every Sidekiq worker' do
'IssuableExportCsvWorker' => 3,
'IssuePlacementWorker' => 3,
'IssueRebalancingWorker' => 3,
+ 'Issues::PlacementWorker' => 3,
+ 'Issues::RebalancingWorker' => 3,
'IterationsUpdateStatusWorker' => 3,
'JiraConnect::SyncBranchWorker' => 3,
'JiraConnect::SyncBuildsWorker' => 3,
diff --git a/spec/workers/integrations/create_external_cross_reference_worker_spec.rb b/spec/workers/integrations/create_external_cross_reference_worker_spec.rb
new file mode 100644
index 00000000000..61723f44aa5
--- /dev/null
+++ b/spec/workers/integrations/create_external_cross_reference_worker_spec.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Integrations::CreateExternalCrossReferenceWorker do
+ include AfterNextHelpers
+ using RSpec::Parameterized::TableSyntax
+
+ let_it_be(:project) { create(:jira_project, :repository) }
+ let_it_be(:author) { create(:user) }
+ let_it_be(:commit) { project.commit }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let_it_be(:note) { create(:note, project: project) }
+ let_it_be(:snippet) { create(:project_snippet, project: project) }
+
+ let(:project_id) { project.id }
+ let(:external_issue_id) { 'JIRA-123' }
+ let(:mentionable_type) { 'Issue' }
+ let(:mentionable_id) { issue.id }
+ let(:author_id) { author.id }
+ let(:job_args) { [project_id, external_issue_id, mentionable_type, mentionable_id, author_id] }
+
+ def perform
+ described_class.new.perform(*job_args)
+ end
+
+ before do
+ allow(Project).to receive(:find_by_id).and_return(project)
+ end
+
+ it_behaves_like 'an idempotent worker' do
+ before do
+ allow(project.external_issue_tracker).to receive(:create_cross_reference_note)
+ end
+
+ it 'can run multiple times with the same arguments' do
+ subject
+
+ expect(project.external_issue_tracker).to have_received(:create_cross_reference_note)
+ .exactly(worker_exec_times).times
+ end
+ end
+
+ it 'has the `until_executed` deduplicate strategy' do
+ expect(described_class.get_deduplicate_strategy).to eq(:until_executed)
+ expect(described_class.get_deduplication_options).to include({ including_scheduled: true })
+ end
+
+ # These are the only models where we currently support cross-references,
+ # although this should be expanded to all `Mentionable` models.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/343975
+ where(:mentionable_type, :mentionable_id) do
+ 'Commit' | lazy { commit.id }
+ 'Issue' | lazy { issue.id }
+ 'MergeRequest' | lazy { merge_request.id }
+ 'Note' | lazy { note.id }
+ 'Snippet' | lazy { snippet.id }
+ end
+
+ with_them do
+ it 'creates a cross reference' do
+ expect(project.external_issue_tracker).to receive(:create_cross_reference_note).with(
+ be_a(ExternalIssue).and(have_attributes(id: external_issue_id, project: project)),
+ be_a(mentionable_type.constantize).and(have_attributes(id: mentionable_id)),
+ be_a(User).and(have_attributes(id: author_id))
+ )
+
+ perform
+ end
+ end
+
+ describe 'error handling' do
+ shared_examples 'does not create a cross reference' do
+ it 'does not create a cross reference' do
+ expect(project).not_to receive(:external_issue_tracker) if project
+
+ perform
+ end
+ end
+
+ context 'project_id does not exist' do
+ let(:project_id) { non_existing_record_id }
+ let(:project) { nil }
+
+ it_behaves_like 'does not create a cross reference'
+ end
+
+ context 'author_id does not exist' do
+ let(:author_id) { non_existing_record_id }
+
+ it_behaves_like 'does not create a cross reference'
+ end
+
+ context 'mentionable_id does not exist' do
+ let(:mentionable_id) { non_existing_record_id }
+
+ it_behaves_like 'does not create a cross reference'
+ end
+
+ context 'mentionable_type is not a Mentionable' do
+ let(:mentionable_type) { 'User' }
+
+ before do
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(kind_of(ArgumentError))
+ end
+
+ it_behaves_like 'does not create a cross reference'
+ end
+
+ context 'mentionable_type is not a defined constant' do
+ let(:mentionable_type) { 'FooBar' }
+
+ before do
+ expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with(kind_of(ArgumentError))
+ end
+
+ it_behaves_like 'does not create a cross reference'
+ end
+
+ context 'mentionable is a Commit and mentionable_id does not exist' do
+ let(:mentionable_type) { 'Commit' }
+ let(:mentionable_id) { non_existing_record_id }
+
+ it_behaves_like 'does not create a cross reference'
+ end
+ end
+end
diff --git a/spec/workers/issue_rebalancing_worker_spec.rb b/spec/workers/issue_rebalancing_worker_spec.rb
index cba42a1577e..cfb19af05b3 100644
--- a/spec/workers/issue_rebalancing_worker_spec.rb
+++ b/spec/workers/issue_rebalancing_worker_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe IssueRebalancingWorker do
+RSpec.describe IssueRebalancingWorker, :clean_gitlab_redis_shared_state do
describe '#perform' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
@@ -35,6 +35,20 @@ RSpec.describe IssueRebalancingWorker do
described_class.new.perform # all arguments are nil
end
+
+ it 'does not schedule a new rebalance if it finished under 1h ago' do
+ container_type = arguments.second.present? ? ::Gitlab::Issues::Rebalancing::State::PROJECT : ::Gitlab::Issues::Rebalancing::State::NAMESPACE
+ container_id = arguments.second || arguments.third
+
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set(::Gitlab::Issues::Rebalancing::State.send(:recently_finished_key, container_type, container_id), true)
+ end
+
+ expect(Issues::RelativePositionRebalancingService).not_to receive(:new)
+ expect(Gitlab::ErrorTracking).not_to receive(:log_exception)
+
+ described_class.new.perform(*arguments)
+ end
end
shared_examples 'safely handles non-existent ids' do
diff --git a/spec/workers/issues/placement_worker_spec.rb b/spec/workers/issues/placement_worker_spec.rb
new file mode 100644
index 00000000000..694cdd2ef37
--- /dev/null
+++ b/spec/workers/issues/placement_worker_spec.rb
@@ -0,0 +1,151 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Issues::PlacementWorker do
+ describe '#perform' do
+ let_it_be(:time) { Time.now.utc }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:author) { create(:user) }
+ let_it_be(:common_attrs) { { author: author, project: project } }
+ let_it_be(:unplaced) { common_attrs.merge(relative_position: nil) }
+ let_it_be_with_reload(:issue) { create(:issue, **unplaced, created_at: time) }
+ let_it_be_with_reload(:issue_a) { create(:issue, **unplaced, created_at: time - 1.minute) }
+ let_it_be_with_reload(:issue_b) { create(:issue, **unplaced, created_at: time - 2.minutes) }
+ let_it_be_with_reload(:issue_c) { create(:issue, **unplaced, created_at: time + 1.minute) }
+ let_it_be_with_reload(:issue_d) { create(:issue, **unplaced, created_at: time + 2.minutes) }
+ let_it_be_with_reload(:issue_e) { create(:issue, **common_attrs, relative_position: 10, created_at: time + 1.minute) }
+ let_it_be_with_reload(:issue_f) { create(:issue, **unplaced, created_at: time + 1.minute) }
+
+ let_it_be(:irrelevant) { create(:issue, relative_position: nil, created_at: time) }
+
+ shared_examples 'running the issue placement worker' do
+ let(:issue_id) { issue.id }
+ let(:project_id) { project.id }
+
+ it 'places all issues created at most 5 minutes before this one at the end, most recent last' do
+ expect { run_worker }.not_to change { irrelevant.reset.relative_position }
+
+ expect(project.issues.order_by_relative_position)
+ .to eq([issue_e, issue_b, issue_a, issue, issue_c, issue_f, issue_d])
+ expect(project.issues.where(relative_position: nil)).not_to exist
+ end
+
+ it 'schedules rebalancing if needed' do
+ issue_a.update!(relative_position: RelativePositioning::MAX_POSITION)
+
+ expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, nil, project.group.id)
+
+ run_worker
+ end
+
+ context 'there are more than QUERY_LIMIT unplaced issues' do
+ before_all do
+ # Ensure there are more than N issues in this set
+ n = described_class::QUERY_LIMIT
+ create_list(:issue, n - 5, **unplaced)
+ end
+
+ it 'limits the sweep to QUERY_LIMIT records, and reschedules placement' do
+ expect(Issue).to receive(:move_nulls_to_end)
+ .with(have_attributes(count: described_class::QUERY_LIMIT))
+ .and_call_original
+
+ expect(described_class).to receive(:perform_async).with(nil, project.id)
+
+ run_worker
+
+ expect(project.issues.where(relative_position: nil)).to exist
+ end
+
+ it 'is eventually correct' do
+ prefix = project.issues.where.not(relative_position: nil).order(:relative_position).to_a
+ moved = project.issues.where.not(id: prefix.map(&:id))
+
+ run_worker
+
+ expect(project.issues.where(relative_position: nil)).to exist
+
+ run_worker
+
+ expect(project.issues.where(relative_position: nil)).not_to exist
+ expect(project.issues.order(:relative_position)).to eq(prefix + moved.order(:created_at, :id))
+ end
+ end
+
+ context 'we are passed bad IDs' do
+ let(:issue_id) { non_existing_record_id }
+ let(:project_id) { non_existing_record_id }
+
+ def max_positions_by_project
+ Issue
+ .group(:project_id)
+ .pluck(:project_id, Issue.arel_table[:relative_position].maximum.as('max_relative_position'))
+ .to_h
+ end
+
+ it 'does move any issues to the end' do
+ expect { run_worker }.not_to change { max_positions_by_project }
+ end
+
+ context 'the project_id refers to an empty project' do
+ let!(:project_id) { create(:project).id }
+
+ it 'does move any issues to the end' do
+ expect { run_worker }.not_to change { max_positions_by_project }
+ end
+ end
+ end
+
+ it 'anticipates the failure to place the issues, and schedules rebalancing' do
+ allow(Issue).to receive(:move_nulls_to_end) { raise RelativePositioning::NoSpaceLeft }
+
+ expect(Issues::RebalancingWorker).to receive(:perform_async).with(nil, nil, project.group.id)
+ expect(Gitlab::ErrorTracking)
+ .to receive(:log_exception)
+ .with(RelativePositioning::NoSpaceLeft, worker_arguments)
+
+ run_worker
+ end
+ end
+
+ context 'passing an issue ID' do
+ def run_worker
+ described_class.new.perform(issue_id)
+ end
+
+ let(:worker_arguments) { { issue_id: issue_id, project_id: nil } }
+
+ it_behaves_like 'running the issue placement worker'
+
+ context 'when block_issue_repositioning is enabled' do
+ let(:issue_id) { issue.id }
+ let(:project_id) { project.id }
+
+ before do
+ stub_feature_flags(block_issue_repositioning: group)
+ end
+
+ it 'does not run repositioning tasks' do
+ expect { run_worker }.not_to change { issue.reset.relative_position }
+ end
+ end
+ end
+
+ context 'passing a project ID' do
+ def run_worker
+ described_class.new.perform(nil, project_id)
+ end
+
+ let(:worker_arguments) { { issue_id: nil, project_id: project_id } }
+
+ it_behaves_like 'running the issue placement worker'
+ end
+ end
+
+ it 'has the `until_executed` deduplicate strategy' do
+ expect(described_class.get_deduplicate_strategy).to eq(:until_executed)
+ expect(described_class.get_deduplication_options).to include({ including_scheduled: true })
+ end
+end
diff --git a/spec/workers/issues/rebalancing_worker_spec.rb b/spec/workers/issues/rebalancing_worker_spec.rb
new file mode 100644
index 00000000000..438edd85f66
--- /dev/null
+++ b/spec/workers/issues/rebalancing_worker_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Issues::RebalancingWorker do
+ describe '#perform' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+ let_it_be(:issue) { create(:issue, project: project) }
+
+ shared_examples 'running the worker' do
+ it 'runs an instance of Issues::RelativePositionRebalancingService' do
+ service = double(execute: nil)
+ service_param = arguments.second.present? ? kind_of(Project.id_in([project]).class) : kind_of(group&.all_projects.class)
+
+ expect(Issues::RelativePositionRebalancingService).to receive(:new).with(service_param).and_return(service)
+
+ described_class.new.perform(*arguments)
+ end
+
+ it 'anticipates there being too many concurent rebalances' do
+ service = double
+ service_param = arguments.second.present? ? kind_of(Project.id_in([project]).class) : kind_of(group&.all_projects.class)
+
+ allow(service).to receive(:execute).and_raise(Issues::RelativePositionRebalancingService::TooManyConcurrentRebalances)
+ expect(Issues::RelativePositionRebalancingService).to receive(:new).with(service_param).and_return(service)
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(Issues::RelativePositionRebalancingService::TooManyConcurrentRebalances, include(project_id: arguments.second, root_namespace_id: arguments.third))
+
+ described_class.new.perform(*arguments)
+ end
+
+ it 'takes no action if the value is nil' do
+ expect(Issues::RelativePositionRebalancingService).not_to receive(:new)
+ expect(Gitlab::ErrorTracking).not_to receive(:log_exception)
+
+ described_class.new.perform # all arguments are nil
+ end
+ end
+
+ shared_examples 'safely handles non-existent ids' do
+ it 'anticipates the inability to find the issue' do
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(ArgumentError, include(project_id: arguments.second, root_namespace_id: arguments.third))
+ expect(Issues::RelativePositionRebalancingService).not_to receive(:new)
+
+ described_class.new.perform(*arguments)
+ end
+ end
+
+ context 'without root_namespace param' do
+ it_behaves_like 'running the worker' do
+ let(:arguments) { [-1, project.id] }
+ end
+
+ it_behaves_like 'safely handles non-existent ids' do
+ let(:arguments) { [nil, -1] }
+ end
+
+ include_examples 'an idempotent worker' do
+ let(:job_args) { [-1, project.id] }
+ end
+
+ include_examples 'an idempotent worker' do
+ let(:job_args) { [nil, -1] }
+ end
+ end
+
+ context 'with root_namespace param' do
+ it_behaves_like 'running the worker' do
+ let(:arguments) { [nil, nil, group.id] }
+ end
+
+ it_behaves_like 'safely handles non-existent ids' do
+ let(:arguments) { [nil, nil, -1] }
+ end
+
+ include_examples 'an idempotent worker' do
+ let(:job_args) { [nil, nil, group.id] }
+ end
+
+ include_examples 'an idempotent worker' do
+ let(:job_args) { [nil, nil, -1] }
+ end
+ end
+ end
+
+ it 'has the `until_executed` deduplicate strategy' do
+ expect(described_class.get_deduplicate_strategy).to eq(:until_executed)
+ expect(described_class.get_deduplication_options).to include({ including_scheduled: true })
+ end
+end
diff --git a/spec/workers/issues/reschedule_stuck_issue_rebalances_worker_spec.rb b/spec/workers/issues/reschedule_stuck_issue_rebalances_worker_spec.rb
new file mode 100644
index 00000000000..02d1241d2ba
--- /dev/null
+++ b/spec/workers/issues/reschedule_stuck_issue_rebalances_worker_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Issues::RescheduleStuckIssueRebalancesWorker, :clean_gitlab_redis_shared_state do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project, group: group) }
+
+ subject(:worker) { described_class.new }
+
+ describe '#perform' do
+ it 'does not schedule a rebalance' do
+ expect(IssueRebalancingWorker).not_to receive(:perform_async)
+
+ worker.perform
+ end
+
+ it 'schedules a rebalance in case there are any rebalances started' do
+ expect(::Gitlab::Issues::Rebalancing::State).to receive(:fetch_rebalancing_groups_and_projects).and_return([[group.id], [project.id]])
+ expect(IssueRebalancingWorker).to receive(:bulk_perform_async).with([[nil, nil, group.id]]).once
+ expect(IssueRebalancingWorker).to receive(:bulk_perform_async).with([[nil, project.id, nil]]).once
+
+ worker.perform
+ end
+ end
+end
diff --git a/spec/workers/loose_foreign_keys/cleanup_worker_spec.rb b/spec/workers/loose_foreign_keys/cleanup_worker_spec.rb
new file mode 100644
index 00000000000..544be2a69a6
--- /dev/null
+++ b/spec/workers/loose_foreign_keys/cleanup_worker_spec.rb
@@ -0,0 +1,153 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe LooseForeignKeys::CleanupWorker do
+ include MigrationsHelpers
+
+ def create_table_structure
+ migration = ActiveRecord::Migration.new.extend(Gitlab::Database::MigrationHelpers::LooseForeignKeyHelpers)
+
+ migration.create_table :_test_loose_fk_parent_table_1
+ migration.create_table :_test_loose_fk_parent_table_2
+
+ migration.create_table :_test_loose_fk_child_table_1_1 do |t|
+ t.bigint :parent_id
+ end
+
+ migration.create_table :_test_loose_fk_child_table_1_2 do |t|
+ t.bigint :parent_id_with_different_column
+ end
+
+ migration.create_table :_test_loose_fk_child_table_2_1 do |t|
+ t.bigint :parent_id
+ end
+
+ migration.track_record_deletions(:_test_loose_fk_parent_table_1)
+ migration.track_record_deletions(:_test_loose_fk_parent_table_2)
+ end
+
+ let!(:parent_model_1) do
+ Class.new(ApplicationRecord) do
+ self.table_name = '_test_loose_fk_parent_table_1'
+
+ include LooseForeignKey
+
+ loose_foreign_key :_test_loose_fk_child_table_1_1, :parent_id, on_delete: :async_delete
+ loose_foreign_key :_test_loose_fk_child_table_1_2, :parent_id_with_different_column, on_delete: :async_nullify
+ end
+ end
+
+ let!(:parent_model_2) do
+ Class.new(ApplicationRecord) do
+ self.table_name = '_test_loose_fk_parent_table_2'
+
+ include LooseForeignKey
+
+ loose_foreign_key :_test_loose_fk_child_table_2_1, :parent_id, on_delete: :async_delete
+ end
+ end
+
+ let!(:child_model_1) do
+ Class.new(ApplicationRecord) do
+ self.table_name = '_test_loose_fk_child_table_1_1'
+ end
+ end
+
+ let!(:child_model_2) do
+ Class.new(ApplicationRecord) do
+ self.table_name = '_test_loose_fk_child_table_1_2'
+ end
+ end
+
+ let!(:child_model_3) do
+ Class.new(ApplicationRecord) do
+ self.table_name = '_test_loose_fk_child_table_2_1'
+ end
+ end
+
+ let(:loose_fk_parent_table_1) { table(:_test_loose_fk_parent_table_1) }
+ let(:loose_fk_parent_table_2) { table(:_test_loose_fk_parent_table_2) }
+ let(:loose_fk_child_table_1_1) { table(:_test_loose_fk_child_table_1_1) }
+ let(:loose_fk_child_table_1_2) { table(:_test_loose_fk_child_table_1_2) }
+ let(:loose_fk_child_table_2_1) { table(:_test_loose_fk_child_table_2_1) }
+
+ before(:all) do
+ create_table_structure
+ end
+
+ after(:all) do
+ migration = ActiveRecord::Migration.new
+
+ migration.drop_table :_test_loose_fk_parent_table_1
+ migration.drop_table :_test_loose_fk_parent_table_2
+ migration.drop_table :_test_loose_fk_child_table_1_1
+ migration.drop_table :_test_loose_fk_child_table_1_2
+ migration.drop_table :_test_loose_fk_child_table_2_1
+ end
+
+ before do
+ parent_record_1 = loose_fk_parent_table_1.create!
+ loose_fk_child_table_1_1.create!(parent_id: parent_record_1.id)
+ loose_fk_child_table_1_2.create!(parent_id_with_different_column: parent_record_1.id)
+
+ parent_record_2 = loose_fk_parent_table_1.create!
+ 2.times { loose_fk_child_table_1_1.create!(parent_id: parent_record_2.id) }
+ 3.times { loose_fk_child_table_1_2.create!(parent_id_with_different_column: parent_record_2.id) }
+
+ parent_record_3 = loose_fk_parent_table_2.create!
+ 5.times { loose_fk_child_table_2_1.create!(parent_id: parent_record_3.id) }
+
+ parent_model_1.delete_all
+ parent_model_2.delete_all
+ end
+
+ it 'cleans up all rows' do
+ described_class.new.perform
+
+ expect(loose_fk_child_table_1_1.count).to eq(0)
+ expect(loose_fk_child_table_1_2.where(parent_id_with_different_column: nil).count).to eq(4)
+ expect(loose_fk_child_table_2_1.count).to eq(0)
+ end
+
+ context 'when deleting in batches' do
+ before do
+ stub_const('LooseForeignKeys::CleanupWorker::BATCH_SIZE', 2)
+ end
+
+ it 'cleans up all rows' do
+ expect(LooseForeignKeys::BatchCleanerService).to receive(:new).exactly(:twice).and_call_original
+
+ described_class.new.perform
+
+ expect(loose_fk_child_table_1_1.count).to eq(0)
+ expect(loose_fk_child_table_1_2.where(parent_id_with_different_column: nil).count).to eq(4)
+ expect(loose_fk_child_table_2_1.count).to eq(0)
+ end
+ end
+
+ context 'when the deleted rows count limit have been reached' do
+ def count_deletable_rows
+ loose_fk_child_table_1_1.count + loose_fk_child_table_2_1.count
+ end
+
+ before do
+ stub_const('LooseForeignKeys::ModificationTracker::MAX_DELETES', 2)
+ stub_const('LooseForeignKeys::CleanerService::DELETE_LIMIT', 1)
+ end
+
+ it 'cleans up 2 rows' do
+ expect { described_class.new.perform }.to change { count_deletable_rows }.by(-2)
+ end
+ end
+
+ context 'when the loose_foreign_key_cleanup feature flag is off' do
+ before do
+ stub_feature_flags(loose_foreign_key_cleanup: false)
+ end
+
+ it 'does nothing' do
+ expect { described_class.new.perform }.not_to change { LooseForeignKeys::DeletedRecord.status_processed.count }
+ end
+ end
+end
diff --git a/spec/workers/namespaces/invite_team_email_worker_spec.rb b/spec/workers/namespaces/invite_team_email_worker_spec.rb
new file mode 100644
index 00000000000..47fdff9a8ef
--- /dev/null
+++ b/spec/workers/namespaces/invite_team_email_worker_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Namespaces::InviteTeamEmailWorker do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+
+ it 'sends the email' do
+ expect(Namespaces::InviteTeamEmailService).to receive(:send_email).with(user, group).once
+ subject.perform(group.id, user.id)
+ end
+
+ context 'when user id is non-existent' do
+ it 'does not send the email' do
+ expect(Namespaces::InviteTeamEmailService).not_to receive(:send_email)
+ subject.perform(group.id, non_existing_record_id)
+ end
+ end
+
+ context 'when group id is non-existent' do
+ it 'does not send the email' do
+ expect(Namespaces::InviteTeamEmailService).not_to receive(:send_email)
+ subject.perform(non_existing_record_id, user.id)
+ end
+ end
+end
diff --git a/spec/workers/packages/maven/metadata/sync_worker_spec.rb b/spec/workers/packages/maven/metadata/sync_worker_spec.rb
index 10482b3e327..4b3cc6f964b 100644
--- a/spec/workers/packages/maven/metadata/sync_worker_spec.rb
+++ b/spec/workers/packages/maven/metadata/sync_worker_spec.rb
@@ -8,6 +8,7 @@ RSpec.describe Packages::Maven::Metadata::SyncWorker, type: :worker do
let(:versions) { %w[1.2 1.1 2.1 3.0-SNAPSHOT] }
let(:worker) { described_class.new }
+ let(:data_struct) { Struct.new(:release, :latest, :versions, keyword_init: true) }
describe '#perform' do
let(:user) { create(:user) }
@@ -197,7 +198,7 @@ RSpec.describe Packages::Maven::Metadata::SyncWorker, type: :worker do
def versions_from(xml_content)
xml_doc = Nokogiri::XML(xml_content)
- OpenStruct.new(
+ data_struct.new(
release: xml_doc.xpath('//metadata/versioning/release').first.content,
latest: xml_doc.xpath('//metadata/versioning/latest').first.content,
versions: xml_doc.xpath('//metadata/versioning/versions/version').map(&:content)
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 039f86f1911..42e39c51a88 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -91,14 +91,6 @@ RSpec.describe PostReceive do
perform
end
-
- it 'tracks an event for the empty_repo_upload experiment', :experiment do
- expect_next_instance_of(EmptyRepoUploadExperiment) do |e|
- expect(e).to receive(:track_initial_write)
- end
-
- perform
- end
end
shared_examples 'not updating remote mirrors' do
diff --git a/spec/workers/propagate_integration_group_worker_spec.rb b/spec/workers/propagate_integration_group_worker_spec.rb
index 9d46534df4f..60442438a1d 100644
--- a/spec/workers/propagate_integration_group_worker_spec.rb
+++ b/spec/workers/propagate_integration_group_worker_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe PropagateIntegrationGroupWorker do
end
context 'with a group integration' do
- let_it_be(:integration) { create(:redmine_integration, group: group, project: nil) }
+ let_it_be(:integration) { create(:redmine_integration, :group, group: group) }
it 'calls to BulkCreateIntegrationService' do
expect(BulkCreateIntegrationService).to receive(:new)
diff --git a/spec/workers/propagate_integration_inherit_descendant_worker_spec.rb b/spec/workers/propagate_integration_inherit_descendant_worker_spec.rb
index 8a231d4104c..c9a7bfaa8b6 100644
--- a/spec/workers/propagate_integration_inherit_descendant_worker_spec.rb
+++ b/spec/workers/propagate_integration_inherit_descendant_worker_spec.rb
@@ -5,8 +5,8 @@ require 'spec_helper'
RSpec.describe PropagateIntegrationInheritDescendantWorker do
let_it_be(:group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group) }
- let_it_be(:group_integration) { create(:redmine_integration, group: group, project: nil) }
- let_it_be(:subgroup_integration) { create(:redmine_integration, group: subgroup, project: nil, inherit_from_id: group_integration.id) }
+ let_it_be(:group_integration) { create(:redmine_integration, :group, group: group) }
+ let_it_be(:subgroup_integration) { create(:redmine_integration, :group, group: subgroup, inherit_from_id: group_integration.id) }
it_behaves_like 'an idempotent worker' do
let(:job_args) { [group_integration.id, subgroup_integration.id, subgroup_integration.id] }
diff --git a/spec/workers/propagate_integration_project_worker_spec.rb b/spec/workers/propagate_integration_project_worker_spec.rb
index 312631252cc..c7adf1b826f 100644
--- a/spec/workers/propagate_integration_project_worker_spec.rb
+++ b/spec/workers/propagate_integration_project_worker_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe PropagateIntegrationProjectWorker do
end
context 'with a group integration' do
- let_it_be(:integration) { create(:redmine_integration, group: group, project: nil) }
+ let_it_be(:integration) { create(:redmine_integration, :group, group: group) }
it 'calls to BulkCreateIntegrationService' do
expect(BulkCreateIntegrationService).to receive(:new)
diff --git a/spec/workers/ssh_keys/expired_notification_worker_spec.rb b/spec/workers/ssh_keys/expired_notification_worker_spec.rb
index 109d24f03ab..be38391ff8c 100644
--- a/spec/workers/ssh_keys/expired_notification_worker_spec.rb
+++ b/spec/workers/ssh_keys/expired_notification_worker_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe SshKeys::ExpiredNotificationWorker, type: :worker do
stub_const("SshKeys::ExpiredNotificationWorker::BATCH_SIZE", 5)
end
- let_it_be_with_reload(:keys) { create_list(:key, 20, expires_at: 3.days.ago, user: user) }
+ let_it_be_with_reload(:keys) { create_list(:key, 20, expires_at: Time.current, user: user) }
it 'updates all keys regardless of batch size' do
worker.perform
@@ -54,8 +54,8 @@ RSpec.describe SshKeys::ExpiredNotificationWorker, type: :worker do
context 'when key has expired in the past' do
let_it_be(:expired_past) { create(:key, expires_at: 1.day.ago, user: user) }
- it 'does update notified column' do
- expect { worker.perform }.to change { expired_past.reload.expiry_notification_delivered_at }
+ it 'does not update notified column' do
+ expect { worker.perform }.not_to change { expired_past.reload.expiry_notification_delivered_at }
end
context 'when key has already been notified of expiration' do
diff --git a/spec/workers/tasks_to_be_done/create_worker_spec.rb b/spec/workers/tasks_to_be_done/create_worker_spec.rb
new file mode 100644
index 00000000000..a158872273f
--- /dev/null
+++ b/spec/workers/tasks_to_be_done/create_worker_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe TasksToBeDone::CreateWorker do
+ let_it_be(:member_task) { create(:member_task, tasks: MemberTask::TASKS.values) }
+ let_it_be(:current_user) { create(:user) }
+
+ let(:assignee_ids) { [1, 2] }
+ let(:job_args) { [member_task.id, current_user.id, assignee_ids] }
+
+ before do
+ member_task.project.group.add_owner(current_user)
+ end
+
+ describe '.perform' do
+ it 'executes the task services for all tasks to be done', :aggregate_failures do
+ MemberTask::TASKS.each_key do |task|
+ service_class = "TasksToBeDone::Create#{task.to_s.camelize}TaskService".constantize
+
+ expect(service_class)
+ .to receive(:new)
+ .with(project: member_task.project, current_user: current_user, assignee_ids: assignee_ids)
+ .and_call_original
+ end
+
+ expect { described_class.new.perform(*job_args) }.to change(Issue, :count).by(3)
+ end
+ end
+
+ include_examples 'an idempotent worker' do
+ it 'creates 3 task issues' do
+ expect { subject }.to change(Issue, :count).by(3)
+ end
+ end
+end
diff --git a/spec/workers/users/deactivate_dormant_users_worker_spec.rb b/spec/workers/users/deactivate_dormant_users_worker_spec.rb
index 934c497c79a..20cd55e19eb 100644
--- a/spec/workers/users/deactivate_dormant_users_worker_spec.rb
+++ b/spec/workers/users/deactivate_dormant_users_worker_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Users::DeactivateDormantUsersWorker do
+ using RSpec::Parameterized::TableSyntax
+
describe '#perform' do
let_it_be(:dormant) { create(:user, last_activity_on: User::MINIMUM_INACTIVE_DAYS.days.ago.to_date) }
let_it_be(:inactive) { create(:user, last_activity_on: nil) }
@@ -22,12 +24,12 @@ RSpec.describe Users::DeactivateDormantUsersWorker do
context 'when automatic deactivation of dormant users is enabled' do
before do
stub_application_setting(deactivate_dormant_users: true)
+ stub_const("#{described_class.name}::PAUSE_SECONDS", 0)
end
it 'deactivates dormant users' do
freeze_time do
stub_const("#{described_class.name}::BATCH_SIZE", 1)
- stub_const("#{described_class.name}::PAUSE_SECONDS", 0)
expect(worker).to receive(:sleep).twice
@@ -37,6 +39,38 @@ RSpec.describe Users::DeactivateDormantUsersWorker do
expect(User.with_no_activity.count).to eq(0)
end
end
+
+ where(:user_type, :expected_state) do
+ :human | 'deactivated'
+ :support_bot | 'active'
+ :alert_bot | 'active'
+ :visual_review_bot | 'active'
+ :service_user | 'deactivated'
+ :ghost | 'active'
+ :project_bot | 'active'
+ :migration_bot | 'active'
+ :security_bot | 'active'
+ :automation_bot | 'active'
+ end
+ with_them do
+ it 'deactivates certain user types' do
+ user = create(:user, user_type: user_type, state: :active, last_activity_on: User::MINIMUM_INACTIVE_DAYS.days.ago.to_date)
+
+ worker.perform
+
+ expect(user.reload.state).to eq(expected_state)
+ end
+ end
+
+ it 'does not deactivate non-active users' do
+ human_user = create(:user, user_type: :human, state: :blocked, last_activity_on: User::MINIMUM_INACTIVE_DAYS.days.ago.to_date)
+ service_user = create(:user, user_type: :service_user, state: :blocked, last_activity_on: User::MINIMUM_INACTIVE_DAYS.days.ago.to_date)
+
+ worker.perform
+
+ expect(human_user.reload.state).to eq('blocked')
+ expect(service_user.reload.state).to eq('blocked')
+ end
end
context 'when automatic deactivation of dormant users is disabled' do