Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2024-01-17 18:10:08 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2024-01-17 18:10:08 +0300
commit78a5f872de316860ccd7a983c10805bf6c6b771c (patch)
tree29c394a4114d012cf9dcef37037e1992ef15105d /spec
parent14c3ebc6364f7d5eb31cbf2e66a79ec574e88b70 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js22
-rw-r--r--spec/graphql/types/work_items/widgets/participants_type_spec.rb11
-rw-r--r--spec/models/concerns/participable_spec.rb143
-rw-r--r--spec/models/work_item_spec.rb19
-rw-r--r--spec/models/work_items/widget_definition_spec.rb3
-rw-r--r--spec/requests/api/graphql/project/work_items_spec.rb84
-rw-r--r--spec/requests/api/graphql/work_item_spec.rb2
-rw-r--r--spec/services/merge_requests/retarget_chain_service_spec.rb14
-rw-r--r--spec/workers/click_house/event_authors_consistency_cron_worker_spec.rb8
-rw-r--r--spec/workers/click_house/event_paths_consistency_cron_worker_spec.rb129
10 files changed, 402 insertions, 33 deletions
diff --git a/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js b/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js
index b19095cc686..48c01e3efad 100644
--- a/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js
@@ -162,4 +162,26 @@ describe('Merge request merge checks component', () => {
expect(wrapper.findByTestId('merge-checks-full').exists()).toBe(true);
});
+
+ it('sorts merge checks', async () => {
+ mountComponent({
+ mergeabilityChecks: [
+ { identifier: 'discussions', status: 'SUCCESS' },
+ { identifier: 'discussions', status: 'INACTIVE' },
+ { identifier: 'rebase', status: 'FAILED' },
+ ],
+ });
+
+ await waitForPromises();
+
+ await wrapper.findByTestId('widget-toggle').trigger('click');
+
+ const mergeChecks = wrapper.findAllByTestId('merge-check');
+
+ expect(mergeChecks.length).toBe(2);
+ expect(mergeChecks.at(0).props('check')).toEqual(expect.objectContaining({ status: 'FAILED' }));
+ expect(mergeChecks.at(1).props('check')).toEqual(
+ expect.objectContaining({ status: 'SUCCESS' }),
+ );
+ });
});
diff --git a/spec/graphql/types/work_items/widgets/participants_type_spec.rb b/spec/graphql/types/work_items/widgets/participants_type_spec.rb
new file mode 100644
index 00000000000..68d201cc52b
--- /dev/null
+++ b/spec/graphql/types/work_items/widgets/participants_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::WorkItems::Widgets::ParticipantsType, feature_category: :team_planning do
+ it 'exposes the expected fields' do
+ expected_fields = %i[participants type]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/models/concerns/participable_spec.rb b/spec/models/concerns/participable_spec.rb
index 58a44fec3aa..57cdf0da516 100644
--- a/spec/models/concerns/participable_spec.rb
+++ b/spec/models/concerns/participable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Participable do
+RSpec.describe Participable, feature_category: :team_planning do
let(:model) do
Class.new do
include Participable
@@ -31,7 +31,7 @@ RSpec.describe Participable do
expect(instance).to receive(:foo).and_return(user2)
expect(instance).to receive(:bar).and_return(user3)
- expect(instance).to receive(:project).thrice.and_return(project)
+ expect(instance).to receive(:project).exactly(4).and_return(project)
participants = instance.participants(user1)
@@ -66,7 +66,7 @@ RSpec.describe Participable do
expect(instance).to receive(:foo).and_return(other)
expect(other).to receive(:bar).and_return(user2)
- expect(instance).to receive(:project).thrice.and_return(project)
+ expect(instance).to receive(:project).exactly(4).and_return(project)
expect(instance.participants(user1)).to eq([user2])
end
@@ -86,7 +86,7 @@ RSpec.describe Participable do
instance = model.new
- expect(instance).to receive(:project).thrice.and_return(project)
+ expect(instance).to receive(:project).exactly(4).and_return(project)
instance.participants(user1)
@@ -115,6 +115,46 @@ RSpec.describe Participable do
expect(participants).to contain_exactly(user1)
end
end
+
+ context 'participable is a group level object' do
+ it 'returns the list of participants' do
+ model.participant(:foo)
+ model.participant(:bar)
+
+ user1 = build(:user)
+ user2 = build(:user)
+ user3 = build(:user)
+ group = build(:group, :public)
+ instance = model.new
+
+ expect(instance).to receive(:foo).and_return(user2)
+ expect(instance).to receive(:bar).and_return(user3)
+ expect(instance).to receive(:project).exactly(3).and_return(nil)
+ expect(instance).to receive(:namespace).exactly(2).and_return(group)
+
+ participants = instance.participants(user1)
+
+ expect(participants).not_to include(user1)
+ expect(participants).to include(user2)
+ expect(participants).to include(user3)
+ end
+ end
+
+ context 'participable is neither a project nor a group level object' do
+ it 'returns no participants' do
+ model.participant(:foo)
+
+ user = build(:user)
+ instance = model.new
+
+ expect(instance).to receive(:foo).and_return(user)
+ expect(instance).to receive(:project).exactly(3).and_return(nil)
+
+ participants = instance.participants(user)
+
+ expect(participants).to be_empty
+ end
+ end
end
describe '#visible_participants' do
@@ -138,7 +178,7 @@ RSpec.describe Participable do
allow(instance).to receive_message_chain(:model_name, :element) { 'class' }
expect(instance).to receive(:foo).and_return(user2)
expect(instance).to receive(:bar).and_return(user3)
- expect(instance).to receive(:project).thrice.and_return(project)
+ expect(instance).to receive(:project).exactly(4).and_return(project)
participants = instance.visible_participants(user1)
@@ -158,17 +198,68 @@ RSpec.describe Participable do
allow(instance).to receive_message_chain(:model_name, :element) { 'class' }
allow(instance).to receive(:bar).and_return(user2)
- expect(instance).to receive(:project).thrice.and_return(project)
+ expect(instance).to receive(:project).exactly(4).and_return(project)
expect(instance.visible_participants(user1)).to be_empty
end
end
+ context 'when participable is a group level object' do
+ let(:group) { create(:group, :private) }
+
+ it 'returns the list of participants' do
+ model.participant(:foo)
+ model.participant(:bar)
+
+ user1 = create(:user)
+ user2 = create(:user)
+ user3 = create(:user)
+ instance = model.new
+
+ group.add_reporter(user1)
+ group.add_reporter(user3)
+
+ allow(instance).to receive_message_chain(:model_name, :element) { 'class' }
+ expect(instance).to receive(:foo).and_return(user2)
+ expect(instance).to receive(:bar).and_return(user3)
+ expect(instance).to receive(:project).exactly(3).and_return(nil)
+ expect(instance).to receive(:namespace).exactly(2).and_return(group)
+
+ participants = instance.visible_participants(user1)
+
+ expect(participants).not_to include(user1) # not returned by participant attr
+ expect(participants).not_to include(user2) # not a member of group
+ expect(participants).to include(user3) # member of group
+ end
+ end
+
+ context 'when participable is neither project nor group level object' do
+ let(:group) { create(:group, :private) }
+
+ it 'returns no participants' do
+ model.participant(:foo)
+
+ user = create(:user)
+ instance = model.new
+
+ group.add_reporter(user)
+
+ allow(instance).to receive_message_chain(:model_name, :element) { 'class' }
+ expect(instance).to receive(:foo).and_return(user)
+ expect(instance).to receive(:project).exactly(3).and_return(nil)
+
+ # user is returned by participant attr and is a member of the group,
+ # but participable model is neither a group or project object
+ participants = instance.visible_participants(user)
+ expect(participants).to be_empty
+ end
+ end
+
context 'with multiple system notes from the same author and mentioned_users' do
let!(:user1) { create(:user) }
let!(:user2) { create(:user) }
- it 'skips expensive checks if the author is aleady in participants list' do
+ it 'skips expensive checks if the author is already in participants list' do
model.participant(:notes)
instance = model.new
@@ -215,7 +306,7 @@ RSpec.describe Participable do
it 'caches the list of raw participants' do
expect(instance).to receive(:raw_participants).once.and_return([])
- expect(instance).to receive(:project).twice.and_return(project)
+ expect(instance).to receive(:project).exactly(4).and_return(project)
instance.participant?(user1)
instance.participant?(user1)
@@ -234,5 +325,41 @@ RSpec.describe Participable do
expect(instance.participant?(user3)).to be false
end
end
+
+ context 'when participable is a group level object' do
+ let(:group) { create(:group, :private) }
+
+ before do
+ # we need users to be created to add them as members to the group
+ user1.save!
+ user2.save!
+ user3.save!
+
+ group.add_reporter(user1)
+ group.add_reporter(user2)
+ end
+
+ it 'returns whether the user is a participant' do
+ allow(instance).to receive(:foo).and_return(user1)
+ allow(instance).to receive(:bar).and_return(user3)
+ allow(instance).to receive(:project).and_return(nil)
+ allow(instance).to receive(:namespace).and_return(group)
+
+ expect(instance.participant?(user1)).to be true # returned by participant attr and a member of group
+ expect(instance.participant?(user2)).to be false # returned by participant attr
+ expect(instance.participant?(user3)).to be false # not a member of group
+ end
+
+ context 'when participable is neither project nor group level object' do
+ it 'returns whether the user is a participant' do
+ allow(instance).to receive(:foo).and_return(user1)
+ allow(instance).to receive(:project).and_return(nil)
+
+ # user1 is returned by participant attr and is a member of group,
+ # but participable model is neither a group or project object
+ expect(instance.participant?(user1)).to be false
+ end
+ end
+ end
end
end
diff --git a/spec/models/work_item_spec.rb b/spec/models/work_item_spec.rb
index 843e3d40dc2..eeb1e3699d8 100644
--- a/spec/models/work_item_spec.rb
+++ b/spec/models/work_item_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
using RSpec::Parameterized::TableSyntax
let_it_be(:reusable_project) { create(:project) }
+ let_it_be(:reusable_group) { create(:group) }
describe 'associations' do
it { is_expected.to belong_to(:namespace) }
@@ -725,4 +726,22 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
expect(item4.linked_items_count).to eq(0)
end
end
+
+ context 'work item participants' do
+ context 'project level work item' do
+ let_it_be(:work_item) { create(:work_item, project: reusable_project) }
+
+ it 'has participants' do
+ expect(work_item.participants).to match_array([work_item.author])
+ end
+ end
+
+ context 'group level work item' do
+ let_it_be(:work_item) { create(:work_item, namespace: reusable_group) }
+
+ it 'has participants' do
+ expect(work_item.participants).to match_array([work_item.author])
+ end
+ end
+ end
end
diff --git a/spec/models/work_items/widget_definition_spec.rb b/spec/models/work_items/widget_definition_spec.rb
index 1540ee57ff4..f1f498cef88 100644
--- a/spec/models/work_items/widget_definition_spec.rb
+++ b/spec/models/work_items/widget_definition_spec.rb
@@ -15,7 +15,8 @@ RSpec.describe WorkItems::WidgetDefinition, feature_category: :team_planning do
::WorkItems::Widgets::Notifications,
::WorkItems::Widgets::CurrentUserTodos,
::WorkItems::Widgets::AwardEmoji,
- ::WorkItems::Widgets::LinkedItems
+ ::WorkItems::Widgets::LinkedItems,
+ ::WorkItems::Widgets::Participants
]
if Gitlab.ee?
diff --git a/spec/requests/api/graphql/project/work_items_spec.rb b/spec/requests/api/graphql/project/work_items_spec.rb
index d0f80bcfebe..982696593bb 100644
--- a/spec/requests/api/graphql/project/work_items_spec.rb
+++ b/spec/requests/api/graphql/project/work_items_spec.rb
@@ -279,14 +279,14 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
context 'when fetching work item notifications widget' do
let(:fields) do
<<~GRAPHQL
- nodes {
- widgets {
- type
- ... on WorkItemWidgetNotifications {
- subscribed
- }
+ nodes {
+ widgets {
+ type
+ ... on WorkItemWidgetNotifications {
+ subscribed
}
}
+ }
GRAPHQL
end
@@ -307,22 +307,22 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
context 'when fetching work item award emoji widget' do
let(:fields) do
<<~GRAPHQL
- nodes {
- widgets {
- type
- ... on WorkItemWidgetAwardEmoji {
- awardEmoji {
- nodes {
- name
- emoji
- user { id }
- }
+ nodes {
+ widgets {
+ type
+ ... on WorkItemWidgetAwardEmoji {
+ awardEmoji {
+ nodes {
+ name
+ emoji
+ user { id }
}
- upvotes
- downvotes
}
+ upvotes
+ downvotes
}
}
+ }
GRAPHQL
end
@@ -407,6 +407,54 @@ RSpec.describe 'getting a work item list for a project', feature_category: :team
end
end
+ context 'when fetching work item participants widget' do
+ let_it_be(:other_project) { create(:project, group: group) }
+ let_it_be(:project) { other_project }
+ let_it_be(:users) { create_list(:user, 3) }
+ let_it_be(:work_items) { create_list(:work_item, 3, project: project, assignees: users) }
+
+ let(:fields) do
+ <<~GRAPHQL
+ nodes {
+ id
+ widgets {
+ type
+ ... on WorkItemWidgetParticipants {
+ participants {
+ nodes {
+ id
+ username
+ }
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ before do
+ project.add_guest(current_user)
+ end
+
+ it 'returns participants' do
+ post_graphql(query, current_user: current_user)
+
+ participants_usernames = graphql_dig_at(items_data, 'widgets', 'participants', 'nodes', 'username')
+ expect(participants_usernames).to match_array(work_items.flat_map(&:participants).map(&:username))
+ end
+
+ it 'executes limited number of N+1 queries', :use_sql_query_cache do
+ control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
+ post_graphql(query, current_user: current_user)
+ end
+
+ create_list(:work_item, 2, project: project, assignees: users)
+
+ expect_graphql_errors_to_be_empty
+ expect { post_graphql(query, current_user: current_user) }.not_to exceed_all_query_limit(control)
+ end
+ end
+
def item_ids
graphql_dig_at(items_data, :id)
end
diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb
index c6d44b057a7..e5d8131fc7e 100644
--- a/spec/requests/api/graphql/work_item_spec.rb
+++ b/spec/requests/api/graphql/work_item_spec.rb
@@ -650,7 +650,7 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
let(:first_param) { 1 }
let(:all_records) { [link1, link2] }
- let(:data_path) { ['workItem', 'widgets', "linkedItems", -1] }
+ let(:data_path) { ['workItem', 'widgets', 'linkedItems', -2] }
def widget_fields(args)
query_graphql_field(
diff --git a/spec/services/merge_requests/retarget_chain_service_spec.rb b/spec/services/merge_requests/retarget_chain_service_spec.rb
index ef8cd0a861e..a048c21d39a 100644
--- a/spec/services/merge_requests/retarget_chain_service_spec.rb
+++ b/spec/services/merge_requests/retarget_chain_service_spec.rb
@@ -41,9 +41,13 @@ RSpec.describe MergeRequests::RetargetChainService, feature_category: :code_revi
'target', 'delete',
merge_request.source_branch, merge_request.target_branch)
+ expect(another_merge_request.rebase_jid).to be_blank
+
expect { subject }.to change { another_merge_request.reload.target_branch }
.from(merge_request.source_branch)
.to(merge_request.target_branch)
+
+ expect(another_merge_request.rebase_jid).to be_present
end
end
@@ -132,9 +136,17 @@ RSpec.describe MergeRequests::RetargetChainService, feature_category: :code_revi
merge_request.mark_as_merged
end
- it 'retargets only 4 of them' do
+ it 'retargets and rebases only 4 of them' do
+ expect(many_merge_requests.any? { |mr| mr.reload.rebase_jid.present? }).to be_falsey
+
subject
+ first_four = many_merge_requests.first(4)
+ others = many_merge_requests - first_four
+
+ expect(others.any? { |mr| mr.reload.rebase_jid.present? }).to be_falsey
+ expect(first_four.all? { |mr| mr.reload.rebase_jid.present? }).to be_truthy
+
expect(many_merge_requests.each(&:reload).pluck(:target_branch).tally)
.to eq(
merge_request.source_branch => 6,
diff --git a/spec/workers/click_house/event_authors_consistency_cron_worker_spec.rb b/spec/workers/click_house/event_authors_consistency_cron_worker_spec.rb
index d4fa35b9b82..4d7e0e138e9 100644
--- a/spec/workers/click_house/event_authors_consistency_cron_worker_spec.rb
+++ b/spec/workers/click_house/event_authors_consistency_cron_worker_spec.rb
@@ -73,10 +73,10 @@ RSpec.describe ClickHouse::EventAuthorsConsistencyCronWorker, feature_category:
User.where(id: [user1.id, user2.id]).delete_all
stub_const("#{described_class}::MAX_AUTHOR_DELETIONS", 2)
- stub_const("#{described_class}::POSTGRESQL_BATCH_SIZE", 1)
+ stub_const("#{ClickHouse::Concerns::ConsistencyWorker}::POSTGRESQL_BATCH_SIZE", 1)
expect(worker).to receive(:log_extra_metadata_on_done).with(:result,
- { status: :deletion_limit_reached, deletions: 2 })
+ { status: :limit_reached, modifications: 2 })
worker.perform
@@ -87,13 +87,13 @@ RSpec.describe ClickHouse::EventAuthorsConsistencyCronWorker, feature_category:
context 'when time limit is reached' do
it 'stops the processing earlier' do
- stub_const("#{described_class}::POSTGRESQL_BATCH_SIZE", 1)
+ stub_const("#{ClickHouse::Concerns::ConsistencyWorker}::POSTGRESQL_BATCH_SIZE", 1)
# stop at the third author_id
allow_next_instance_of(Analytics::CycleAnalytics::RuntimeLimiter) do |runtime_limiter|
allow(runtime_limiter).to receive(:over_time?).and_return(false, false, true)
end
- expect(worker).to receive(:log_extra_metadata_on_done).with(:result, { status: :over_time, deletions: 1 })
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:result, { status: :over_time, modifications: 1 })
worker.perform
diff --git a/spec/workers/click_house/event_paths_consistency_cron_worker_spec.rb b/spec/workers/click_house/event_paths_consistency_cron_worker_spec.rb
new file mode 100644
index 00000000000..76ce63ed2e4
--- /dev/null
+++ b/spec/workers/click_house/event_paths_consistency_cron_worker_spec.rb
@@ -0,0 +1,129 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ClickHouse::EventPathsConsistencyCronWorker, feature_category: :value_stream_management do
+ let(:worker) { described_class.new }
+
+ context 'when ClickHouse is disabled' do
+ it 'does nothing' do
+ allow(ClickHouse::Client).to receive(:database_configured?).and_return(false)
+
+ expect(worker).not_to receive(:log_extra_metadata_on_done)
+
+ worker.perform
+ end
+ end
+
+ context 'when the event_sync_worker_for_click_house feature flag is off' do
+ before do
+ stub_feature_flags(event_sync_worker_for_click_house: false)
+ end
+
+ it 'does nothing' do
+ allow(ClickHouse::Client).to receive(:database_configured?).and_return(true)
+
+ expect(worker).not_to receive(:log_extra_metadata_on_done)
+
+ worker.perform
+ end
+ end
+
+ context 'when ClickHouse is available', :click_house do
+ let_it_be(:connection) { ClickHouse::Connection.new(:main) }
+ let_it_be_with_reload(:namespace1) { create(:group) }
+ let_it_be_with_reload(:namespace2) { create(:project).project_namespace }
+ let_it_be_with_reload(:namespace_with_updated_parent) { create(:group, parent: create(:group)) }
+
+ let(:leftover_paths) { connection.select('SELECT DISTINCT path FROM events FINAL').pluck('path') }
+ let(:deleted_namespace_id) { namespace_with_updated_parent.id + 1 }
+
+ before do
+ insert_query = <<~SQL
+ INSERT INTO events (id, path) VALUES
+ (1, '#{namespace1.id}/'),
+ (2, '#{namespace2.traversal_ids.join('/')}/'),
+ (3, '#{namespace1.id}/#{namespace_with_updated_parent.id}/'),
+ (4, '#{deleted_namespace_id}/'),
+ (5, '#{deleted_namespace_id}/')
+ SQL
+
+ connection.execute(insert_query)
+ end
+
+ it 'fixes all inconsistent records in ClickHouse' do
+ worker.perform
+
+ paths = [
+ "#{namespace1.id}/",
+ "#{namespace2.traversal_ids.join('/')}/",
+ "#{namespace_with_updated_parent.traversal_ids.join('/')}/"
+ ]
+
+ expect(leftover_paths).to match_array(paths)
+
+ # the next job starts from the beginning of the table
+ expect(ClickHouse::SyncCursor.cursor_for(:event_namespace_paths_consistency_check)).to eq(0)
+ end
+
+ context 'when the table is empty' do
+ it 'does not do anything' do
+ connection.execute('TRUNCATE TABLE event_namespace_paths')
+
+ expect { worker.perform }.not_to change { connection.select('SELECT * FROM events FINAL ORDER BY id') }
+ end
+ end
+
+ context 'when the previous job was not finished' do
+ it 'continues the processing from the cursor' do
+ ClickHouse::SyncCursor.update_cursor_for(:event_namespace_paths_consistency_check, deleted_namespace_id)
+
+ worker.perform
+
+ paths = [
+ "#{namespace1.id}/",
+ "#{namespace2.traversal_ids.join('/')}/",
+ "#{namespace1.id}/#{namespace_with_updated_parent.id}/"
+ ]
+ # the previous records should remain
+ expect(leftover_paths).to match_array(paths)
+ end
+ end
+
+ context 'when processing stops due to the record clean up limit' do
+ it 'stores the last processed id value' do
+ stub_const("#{described_class}::MAX_RECORD_MODIFICATIONS", 1)
+ stub_const("#{ClickHouse::Concerns::ConsistencyWorker}::POSTGRESQL_BATCH_SIZE", 1)
+
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:result,
+ { status: :modification_limit_reached, modifications: 2 })
+
+ worker.perform
+
+ paths = [
+ "#{namespace1.id}/",
+ "#{namespace2.traversal_ids.join('/')}/",
+ "#{namespace_with_updated_parent.traversal_ids.join('/')}/"
+ ]
+
+ expect(leftover_paths).to match_array(paths)
+ expect(ClickHouse::SyncCursor.cursor_for(:event_namespace_paths_consistency_check)).to eq(deleted_namespace_id)
+ end
+ end
+
+ context 'when the processing stops due to time limit' do
+ it 'returns over_time status' do
+ stub_const("#{ClickHouse::Concerns::ConsistencyWorker}::POSTGRESQL_BATCH_SIZE", 1)
+
+ allow_next_instance_of(Analytics::CycleAnalytics::RuntimeLimiter) do |runtime_limiter|
+ allow(runtime_limiter).to receive(:over_time?).and_return(false, true)
+ end
+
+ expect(worker).to receive(:log_extra_metadata_on_done).with(:result,
+ { status: :over_time, modifications: 1 })
+
+ worker.perform
+ end
+ end
+ end
+end