summaryrefslogtreecommitdiff
path: root/spec/workers/issue_placement_worker_spec.rb
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-19 01:45:44 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-19 01:45:44 +0000
commit85dc423f7090da0a52c73eb66faf22ddb20efff9 (patch)
tree9160f299afd8c80c038f08e1545be119f5e3f1e1 /spec/workers/issue_placement_worker_spec.rb
parent15c2c8c66dbe422588e5411eee7e68f1fa440bb8 (diff)
downloadgitlab-ce-85dc423f7090da0a52c73eb66faf22ddb20efff9.tar.gz
Add latest changes from gitlab-org/gitlab@13-4-stable-ee
Diffstat (limited to 'spec/workers/issue_placement_worker_spec.rb')
-rw-r--r--spec/workers/issue_placement_worker_spec.rb132
1 files changed, 132 insertions, 0 deletions
diff --git a/spec/workers/issue_placement_worker_spec.rb b/spec/workers/issue_placement_worker_spec.rb
new file mode 100644
index 00000000000..5d4d41b90d0
--- /dev/null
+++ b/spec/workers/issue_placement_worker_spec.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe IssuePlacementWorker do
+ describe '#perform' do
+ let_it_be(:time) { Time.now.utc }
+ let_it_be(:project) { create(:project) }
+ 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_relative_position_asc)
+ .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, project.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(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.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'
+ 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
+end