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>2023-06-08 21:08:59 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-08 21:08:59 +0300
commit8b0d3151ae81cef695647771d1781c535d6f6cf5 (patch)
treef58d72ada21f6f7598a1e9f69fc80cdbbae8f2b6 /spec
parentec9dd96cd876d8778bb757a1e1e0252a58fdcbbb (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/graphql_controller_spec.rb36
-rw-r--r--spec/graphql/graphql_triggers_spec.rb30
-rw-r--r--spec/graphql/types/subscription_type_spec.rb1
-rw-r--r--spec/lib/api/entities/merge_request_basic_spec.rb2
-rw-r--r--spec/lib/gitlab/avatar_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/database/partitioning/list/convert_table_spec.rb34
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb1
-rw-r--r--spec/lib/gitlab/instrumentation/redis_spec.rb20
-rw-r--r--spec/lib/gitlab/instrumentation_helper_spec.rb18
-rw-r--r--spec/lib/gitlab/omniauth_initializer_spec.rb8
-rw-r--r--spec/lib/gitlab/patch/redis_cache_store_spec.rb86
-rw-r--r--spec/lib/gitlab/reactive_cache_set_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/cluster_util_spec.rb60
-rw-r--r--spec/lib/gitlab/redis/cross_slot_spec.rb124
-rw-r--r--spec/lib/gitlab/redis/multi_store_spec.rb19
-rw-r--r--spec/lib/gitlab/repository_cache/preloader_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_hash_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb13
-rw-r--r--spec/models/plan_limits_spec.rb3
-rw-r--r--spec/requests/api/graphql/subscriptions/work_item_updated_spec.rb43
-rw-r--r--spec/services/issues/update_service_spec.rb4
-rw-r--r--spec/services/work_items/update_service_spec.rb28
-rw-r--r--spec/support/caching.rb13
-rw-r--r--spec/support/helpers/graphql/subscriptions/work_items/helper.rb60
-rw-r--r--spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb2
-rw-r--r--spec/support/shared_contexts/lib/gitlab/database/partitioning/list_partitioning_shared_context.rb1
-rw-r--r--spec/support/shared_examples/work_items/update_service_shared_examples.rb17
-rw-r--r--spec/tasks/cache_rake_spec.rb8
28 files changed, 533 insertions, 106 deletions
diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb
index 3f7d25e42da..8281cd595f8 100644
--- a/spec/controllers/graphql_controller_spec.rb
+++ b/spec/controllers/graphql_controller_spec.rb
@@ -441,13 +441,39 @@ RSpec.describe GraphqlController, feature_category: :integrations do
post :execute, params: { query: query, operationName: 'IntrospectionQuery' }
end
- it 'does not cache an unknown introspection query' do
- query = File.read(Rails.root.join('spec/fixtures/api/graphql/fake_introspection.graphql'))
-
- expect(GitlabSchema).to receive(:execute).exactly(:twice)
+ it 'logs that it will try to hit the cache' do
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ message: "IntrospectionQueryCache",
+ can_use_introspection_query_cache: true,
+ query: query.to_s,
+ variables: {},
+ introspection_query_cache_key: ['introspection-query-cache', Gitlab.revision, false]
+ )
post :execute, params: { query: query, operationName: 'IntrospectionQuery' }
- post :execute, params: { query: query, operationName: 'IntrospectionQuery' }
+ end
+
+ context 'when there is an unknown introspection query' do
+ let(:query) { File.read(Rails.root.join('spec/fixtures/api/graphql/fake_introspection.graphql')) }
+
+ it 'logs that it did not try to hit the cache' do
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ message: "IntrospectionQueryCache",
+ can_use_introspection_query_cache: false,
+ query: query.to_s,
+ variables: {},
+ introspection_query_cache_key: ['introspection-query-cache', Gitlab.revision, false]
+ )
+
+ post :execute, params: { query: query, operationName: 'IntrospectionQuery' }
+ end
+
+ it 'does not cache an unknown introspection query' do
+ expect(GitlabSchema).to receive(:execute).exactly(:twice)
+
+ post :execute, params: { query: query, operationName: 'IntrospectionQuery' }
+ post :execute, params: { query: query, operationName: 'IntrospectionQuery' }
+ end
end
it 'hits the cache even if the whitespace in the query differs' do
diff --git a/spec/graphql/graphql_triggers_spec.rb b/spec/graphql/graphql_triggers_spec.rb
index a8a37289ddd..f59ce805fcd 100644
--- a/spec/graphql/graphql_triggers_spec.rb
+++ b/spec/graphql/graphql_triggers_spec.rb
@@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe GraphqlTriggers, feature_category: :shared do
- let_it_be(:issuable, refind: true) { create(:work_item) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issuable, refind: true) { create(:work_item, project: project) }
describe '.issuable_assignees_updated' do
let(:assignees) { create_list(:user, 2) }
@@ -144,4 +145,31 @@ RSpec.describe GraphqlTriggers, feature_category: :shared do
GraphqlTriggers.merge_request_approval_state_updated(merge_request)
end
end
+
+ describe '.work_item_updated' do
+ it 'triggers the work_item_updated subscription' do
+ expect(GitlabSchema.subscriptions).to receive(:trigger).with(
+ 'workItemUpdated',
+ { work_item_id: issuable.to_gid },
+ issuable
+ ).and_call_original
+
+ GraphqlTriggers.work_item_updated(issuable)
+ end
+
+ context 'when triggered with an Issue' do
+ it 'triggers the subscription with a work item' do
+ issue = create(:issue, project: project)
+ work_item = WorkItem.find(issue.id)
+
+ expect(GitlabSchema.subscriptions).to receive(:trigger).with(
+ 'workItemUpdated',
+ { work_item_id: work_item.to_gid },
+ work_item
+ ).and_call_original
+
+ GraphqlTriggers.work_item_updated(issue)
+ end
+ end
+ end
end
diff --git a/spec/graphql/types/subscription_type_spec.rb b/spec/graphql/types/subscription_type_spec.rb
index a57a8e751ac..d3e5b6ffa3a 100644
--- a/spec/graphql/types/subscription_type_spec.rb
+++ b/spec/graphql/types/subscription_type_spec.rb
@@ -15,6 +15,7 @@ RSpec.describe GitlabSchema.types['Subscription'] do
merge_request_reviewers_updated
merge_request_merge_status_updated
merge_request_approval_state_updated
+ work_item_updated
]
expect(described_class).to include_graphql_fields(*expected_fields)
diff --git a/spec/lib/api/entities/merge_request_basic_spec.rb b/spec/lib/api/entities/merge_request_basic_spec.rb
index 33f8a806c50..89e19f8529e 100644
--- a/spec/lib/api/entities/merge_request_basic_spec.rb
+++ b/spec/lib/api/entities/merge_request_basic_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe ::API::Entities::MergeRequestBasic do
merged_by merge_user merged_at closed_by closed_at target_branch user_notes_count upvotes downvotes
author assignees assignee reviewers source_project_id target_project_id labels draft work_in_progress
milestone merge_when_pipeline_succeeds merge_status detailed_merge_status sha merge_commit_sha
- squash_commit_sha discussion_locked should_remove_source_branch force_remove_source_branch
+ squash_commit_sha discussion_locked should_remove_source_branch force_remove_source_branch prepared_at
reference references web_url time_stats squash task_completion_status has_conflicts blocking_discussions_resolved
]
diff --git a/spec/lib/gitlab/avatar_cache_spec.rb b/spec/lib/gitlab/avatar_cache_spec.rb
index db2fcf9d140..35ba767a5c5 100644
--- a/spec/lib/gitlab/avatar_cache_spec.rb
+++ b/spec/lib/gitlab/avatar_cache_spec.rb
@@ -103,7 +103,7 @@ RSpec.describe Gitlab::AvatarCache, :clean_gitlab_redis_cache do
context 'when deleting over 1000 emails' do
it 'deletes in batches of 1000' do
Gitlab::Redis::Cache.with do |redis|
- expect(redis).to receive(:pipelined).twice.and_call_original
+ expect(redis).to receive(:pipelined).at_least(2).and_call_original
end
described_class.delete_by_email(*(Array.new(1001) { |i| i }))
diff --git a/spec/lib/gitlab/database/partitioning/list/convert_table_spec.rb b/spec/lib/gitlab/database/partitioning/list/convert_table_spec.rb
index 8e2a53ea76f..b30501cce21 100644
--- a/spec/lib/gitlab/database/partitioning/list/convert_table_spec.rb
+++ b/spec/lib/gitlab/database/partitioning/list/convert_table_spec.rb
@@ -15,8 +15,7 @@ RSpec.describe Gitlab::Database::Partitioning::List::ConvertTable, feature_categ
table_name: table_name,
partitioning_column: partitioning_column,
parent_table_name: parent_table_name,
- zero_partition_value: partitioning_default,
- lock_tables: lock_tables
+ zero_partition_value: partitioning_default
)
end
@@ -227,16 +226,6 @@ RSpec.describe Gitlab::Database::Partitioning::List::ConvertTable, feature_categ
end
end
- context 'with locking tables' do
- let(:lock_tables) { [table_name] }
-
- it 'locks the table' do
- recorder = ActiveRecord::QueryRecorder.new { partition }
-
- expect(recorder.log).to include(/LOCK "_test_table_to_partition" IN ACCESS EXCLUSIVE MODE/)
- end
- end
-
context 'when an error occurs during the conversion' do
before do
# Set up the fault that we'd like to inject
@@ -264,7 +253,6 @@ RSpec.describe Gitlab::Database::Partitioning::List::ConvertTable, feature_categ
with_them do
it 'recovers from a fault', :aggregate_failures do
expect { converter.partition }.to raise_error(/fault/)
- expect(Gitlab::Database::PostgresPartition.for_parent_table(parent_table_name).count).to eq(0)
expect { converter.partition }.not_to raise_error
expect(Gitlab::Database::PostgresPartition.for_parent_table(parent_table_name).count).to eq(1)
@@ -286,26 +274,6 @@ RSpec.describe Gitlab::Database::Partitioning::List::ConvertTable, feature_categ
expect(migration_context.has_loose_foreign_key?(table_name)).to be_truthy
expect(migration_context.has_loose_foreign_key?(parent_table_name)).to be_truthy
end
-
- context 'with locking tables' do
- let(:lock_tables) { [table_name] }
-
- it 'locks the table before dropping the triggers' do
- recorder = ActiveRecord::QueryRecorder.new { partition }
-
- lock_index = recorder.log.find_index do |log|
- log.start_with?('LOCK "_test_table_to_partition" IN ACCESS EXCLUSIVE MODE')
- end
-
- trigger_index = recorder.log.find_index do |log|
- log.start_with?('DROP TRIGGER IF EXISTS _test_table_to_partition_loose_fk_trigger')
- end
-
- expect(lock_index).to be_present
- expect(trigger_index).to be_present
- expect(lock_index).to be < trigger_index
- end
- end
end
end
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
index 571c67db597..6a947044317 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
@@ -68,7 +68,6 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
describe '#convert_table_to_first_list_partition' do
it_behaves_like 'delegates to ConvertTable' do
let(:lock_tables) { [source_table] }
- let(:extra_options) { { lock_tables: lock_tables } }
let(:expected_method) { :partition }
let(:migrate) do
migration.convert_table_to_first_list_partition(table_name: source_table,
diff --git a/spec/lib/gitlab/instrumentation/redis_spec.rb b/spec/lib/gitlab/instrumentation/redis_spec.rb
index 3e02eadba4b..1b7774bc229 100644
--- a/spec/lib/gitlab/instrumentation/redis_spec.rb
+++ b/spec/lib/gitlab/instrumentation/redis_spec.rb
@@ -35,13 +35,13 @@ RSpec.describe Gitlab::Instrumentation::Redis do
# will be an extra SELECT command to choose the right database. We
# don't want to make the spec less precise, so we force that to
# happen (if needed) first, then clear the counts.
- Gitlab::Redis::Cache.with { |redis| redis.info }
+ Gitlab::Redis::Sessions.with { |redis| redis.info }
RequestStore.clear!
stub_rails_env('staging') # to avoid raising CrossSlotError
- Gitlab::Redis::Cache.with { |redis| redis.mset('cache-test', 321, 'cache-test-2', 321) }
+ Gitlab::Redis::Sessions.with { |redis| redis.mset('cache-test', 321, 'cache-test-2', 321) }
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
- Gitlab::Redis::Cache.with { |redis| redis.mget('cache-test', 'cache-test-2') }
+ Gitlab::Redis::Sessions.with { |redis| redis.mget('cache-test', 'cache-test-2') }
end
Gitlab::Redis::SharedState.with { |redis| redis.set('shared-state-test', 123) }
end
@@ -56,13 +56,13 @@ RSpec.describe Gitlab::Instrumentation::Redis do
redis_read_bytes: be >= 0,
redis_write_bytes: be >= 0,
- # Cache results
- redis_cache_calls: 2,
- redis_cache_cross_slot_calls: 1,
- redis_cache_allowed_cross_slot_calls: 1,
- redis_cache_duration_s: be >= 0,
- redis_cache_read_bytes: be >= 0,
- redis_cache_write_bytes: be >= 0,
+ # Queues results
+ redis_sessions_calls: 2,
+ redis_sessions_cross_slot_calls: 1,
+ redis_sessions_allowed_cross_slot_calls: 1,
+ redis_sessions_duration_s: be >= 0,
+ redis_sessions_read_bytes: be >= 0,
+ redis_sessions_write_bytes: be >= 0,
# Shared state results
redis_shared_state_calls: 1,
diff --git a/spec/lib/gitlab/instrumentation_helper_spec.rb b/spec/lib/gitlab/instrumentation_helper_spec.rb
index 8a88328e0c1..698c8a37d48 100644
--- a/spec/lib/gitlab/instrumentation_helper_spec.rb
+++ b/spec/lib/gitlab/instrumentation_helper_spec.rb
@@ -41,9 +41,9 @@ RSpec.describe Gitlab::InstrumentationHelper, :clean_gitlab_redis_repository_cac
context 'when Redis calls are made' do
it 'adds Redis data and omits Gitaly data' do
stub_rails_env('staging') # to avoid raising CrossSlotError
- Gitlab::Redis::Cache.with { |redis| redis.mset('test-cache', 123, 'test-cache2', 123) }
+ Gitlab::Redis::Sessions.with { |redis| redis.mset('test-cache', 123, 'test-cache2', 123) }
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
- Gitlab::Redis::Cache.with { |redis| redis.mget('cache-test', 'cache-test-2') }
+ Gitlab::Redis::Sessions.with { |redis| redis.mget('cache-test', 'cache-test-2') }
end
Gitlab::Redis::Queues.with { |redis| redis.set('test-queues', 321) }
@@ -63,13 +63,13 @@ RSpec.describe Gitlab::InstrumentationHelper, :clean_gitlab_redis_repository_cac
expect(payload[:redis_queues_read_bytes]).to be >= 0
expect(payload[:redis_queues_write_bytes]).to be >= 0
- # Cache payload
- expect(payload[:redis_cache_calls]).to eq(2)
- expect(payload[:redis_cache_cross_slot_calls]).to eq(1)
- expect(payload[:redis_cache_allowed_cross_slot_calls]).to eq(1)
- expect(payload[:redis_cache_duration_s]).to be >= 0
- expect(payload[:redis_cache_read_bytes]).to be >= 0
- expect(payload[:redis_cache_write_bytes]).to be >= 0
+ # Sessions payload
+ expect(payload[:redis_sessions_calls]).to eq(2)
+ expect(payload[:redis_sessions_cross_slot_calls]).to eq(1)
+ expect(payload[:redis_sessions_allowed_cross_slot_calls]).to eq(1)
+ expect(payload[:redis_sessions_duration_s]).to be >= 0
+ expect(payload[:redis_sessions_read_bytes]).to be >= 0
+ expect(payload[:redis_sessions_write_bytes]).to be >= 0
# Gitaly
expect(payload[:gitaly_calls]).to be_nil
diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb
index 112fdb183ab..1c665ec6e18 100644
--- a/spec/lib/gitlab/omniauth_initializer_spec.rb
+++ b/spec/lib/gitlab/omniauth_initializer_spec.rb
@@ -216,6 +216,14 @@ RSpec.describe Gitlab::OmniauthInitializer do
expect { subject.execute([hash_config]) }.to raise_error(NameError)
end
+ it 'configures fail_with_empty_uid for shibboleth' do
+ shibboleth_config = { 'name' => 'shibboleth', 'args' => {} }
+
+ expect(devise_config).to receive(:omniauth).with(:shibboleth, { fail_with_empty_uid: true })
+
+ subject.execute([shibboleth_config])
+ end
+
it 'configures defaults for google_oauth2' do
google_config = {
'name' => 'google_oauth2',
diff --git a/spec/lib/gitlab/patch/redis_cache_store_spec.rb b/spec/lib/gitlab/patch/redis_cache_store_spec.rb
index fdb220276a4..1b3813d03a2 100644
--- a/spec/lib/gitlab/patch/redis_cache_store_spec.rb
+++ b/spec/lib/gitlab/patch/redis_cache_store_spec.rb
@@ -16,7 +16,11 @@ RSpec.describe Gitlab::Patch::RedisCacheStore, :use_clean_rails_redis_caching, f
describe '#read_multi_mget' do
it 'runs multi-key command if no cross-slot command is expected' do
Rails.cache.redis.with do |redis|
- expect(redis).not_to receive(:pipelined)
+ if Gitlab::Redis::ClusterUtil.cluster?(redis)
+ expect(redis).to receive(:pipelined).once.and_call_original
+ else
+ expect(redis).not_to receive(:pipelined)
+ end
end
expect(
@@ -24,13 +28,15 @@ RSpec.describe Gitlab::Patch::RedisCacheStore, :use_clean_rails_redis_caching, f
).to eq({ '{user1}:x' => 1, '{user1}:y' => 2, '{user1}:z' => 3 })
end
- it 'skips patch if input is above 100' do
- Rails.cache.redis.with do |redis|
- expect(redis).not_to receive(:pipelined)
- end
+ context 'when deleting large amount of keys' do
+ it 'batches get into pipelines of 100' do
+ Rails.cache.redis.with do |redis|
+ expect(redis).to receive(:pipelined).at_least(2).and_call_original
+ end
- Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
- Rails.cache.read_multi(*Array.new(101) { |i| i })
+ Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
+ Rails.cache.read_multi(*Array.new(101) { |i| i })
+ end
end
end
@@ -38,19 +44,23 @@ RSpec.describe Gitlab::Patch::RedisCacheStore, :use_clean_rails_redis_caching, f
it 'reads multiple keys' do
if patched
Rails.cache.redis.with do |redis|
- expect(redis).to receive(:pipelined).once.and_call_original
+ expect(redis).to receive(:pipelined).at_least(1).and_call_original
end
end
- expect(::Feature).to receive(:enabled?)
- .with(:feature_flag_state_logs, { default_enabled_if_undefined: nil, type: :ops })
- .exactly(:once)
- .and_call_original
-
- expect(::Feature).to receive(:enabled?)
- .with(:enable_rails_cache_pipeline_patch)
- .exactly(:once)
- .and_call_original
+ Gitlab::Redis::Cache.with do |redis|
+ unless Gitlab::Redis::ClusterUtil.cluster?(redis)
+ expect(::Feature).to receive(:enabled?)
+ .with(:feature_flag_state_logs, { default_enabled_if_undefined: nil, type: :ops })
+ .exactly(:once)
+ .and_call_original
+
+ expect(::Feature).to receive(:enabled?)
+ .with(:enable_rails_cache_pipeline_patch)
+ .exactly(:once)
+ .and_call_original
+ end
+ end
expect(
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
@@ -65,7 +75,7 @@ RSpec.describe Gitlab::Patch::RedisCacheStore, :use_clean_rails_redis_caching, f
shared_examples 'reading using non redis cache stores' do |klass|
it 'does not affect non Redis::Cache cache stores' do
klass.cache_store.redis.with do |redis|
- expect(redis).not_to receive(:pipelined)
+ expect(redis).not_to receive(:pipelined) unless Gitlab::Redis::ClusterUtil.cluster?(redis)
end
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
@@ -75,8 +85,8 @@ RSpec.describe Gitlab::Patch::RedisCacheStore, :use_clean_rails_redis_caching, f
end
context 'when reading from non redis-cache stores' do
- it_behaves_like 'reading using non redis cache stores', Gitlab::Redis::RepositoryCache
it_behaves_like 'reading using non redis cache stores', Gitlab::Redis::FeatureFlag
+ it_behaves_like 'reading using non redis cache stores', Gitlab::Redis::RepositoryCache
end
context 'when feature flag is disabled' do
@@ -95,19 +105,23 @@ RSpec.describe Gitlab::Patch::RedisCacheStore, :use_clean_rails_redis_caching, f
it 'deletes multiple keys' do
if patched
Rails.cache.redis.with do |redis|
- expect(redis).to receive(:pipelined).once.and_call_original
+ expect(redis).to receive(:pipelined).at_least(1).and_call_original
end
end
- expect(::Feature).to receive(:enabled?)
- .with(:feature_flag_state_logs, { default_enabled_if_undefined: nil, type: :ops })
- .exactly(:once)
- .and_call_original
-
- expect(::Feature).to receive(:enabled?)
- .with(:enable_rails_cache_pipeline_patch)
- .exactly(:once)
- .and_call_original
+ Gitlab::Redis::Cache.with do |redis|
+ unless Gitlab::Redis::ClusterUtil.cluster?(redis)
+ expect(::Feature).to receive(:enabled?)
+ .with(:feature_flag_state_logs, { default_enabled_if_undefined: nil, type: :ops })
+ .exactly(:once)
+ .and_call_original
+
+ expect(::Feature).to receive(:enabled?)
+ .with(:enable_rails_cache_pipeline_patch)
+ .exactly(:once)
+ .and_call_original
+ end
+ end
expect(
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
@@ -120,7 +134,7 @@ RSpec.describe Gitlab::Patch::RedisCacheStore, :use_clean_rails_redis_caching, f
shared_examples 'deleting using non redis cache stores' do |klass|
it 'does not affect non Redis::Cache cache stores' do
klass.cache_store.redis.with do |redis|
- expect(redis).not_to receive(:pipelined)
+ expect(redis).not_to receive(:pipelined) unless Gitlab::Redis::ClusterUtil.cluster?(redis)
end
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
@@ -130,8 +144,8 @@ RSpec.describe Gitlab::Patch::RedisCacheStore, :use_clean_rails_redis_caching, f
end
context 'when deleting from non redis-cache stores' do
- it_behaves_like 'deleting using non redis cache stores', Gitlab::Redis::RepositoryCache
it_behaves_like 'deleting using non redis cache stores', Gitlab::Redis::FeatureFlag
+ it_behaves_like 'deleting using non redis cache stores', Gitlab::Redis::RepositoryCache
end
context 'when deleting large amount of keys' do
@@ -141,7 +155,9 @@ RSpec.describe Gitlab::Patch::RedisCacheStore, :use_clean_rails_redis_caching, f
it 'calls pipeline multiple times' do
Rails.cache.redis.with do |redis|
- expect(redis).to receive(:pipelined).twice.and_call_original
+ # no expectation on number of times as it could vary depending on cluster size
+ # if the Redis is a Redis Cluster
+ expect(redis).to receive(:pipelined).at_least(2).and_call_original
end
expect(
@@ -154,7 +170,11 @@ RSpec.describe Gitlab::Patch::RedisCacheStore, :use_clean_rails_redis_caching, f
it 'runs multi-key command if no cross-slot command is expected' do
Rails.cache.redis.with do |redis|
- expect(redis).not_to receive(:pipelined)
+ if Gitlab::Redis::ClusterUtil.cluster?(redis)
+ expect(redis).to receive(:pipelined).once.and_call_original
+ else
+ expect(redis).not_to receive(:pipelined)
+ end
end
expect(
diff --git a/spec/lib/gitlab/reactive_cache_set_cache_spec.rb b/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
index 1a71b23533e..9956078999f 100644
--- a/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
+++ b/spec/lib/gitlab/reactive_cache_set_cache_spec.rb
@@ -75,7 +75,7 @@ RSpec.describe Gitlab::ReactiveCacheSetCache, :clean_gitlab_redis_cache do
it 'sends multiple pipelines of 1000 unlinks' do
Gitlab::Redis::Cache.with do |redis|
- expect(redis).to receive(:pipelined).twice.and_call_original
+ expect(redis).to receive(:pipelined).at_least(2).and_call_original
end
cache.clear_cache!(cache_prefix)
diff --git a/spec/lib/gitlab/redis/cluster_util_spec.rb b/spec/lib/gitlab/redis/cluster_util_spec.rb
new file mode 100644
index 00000000000..3993004518d
--- /dev/null
+++ b/spec/lib/gitlab/redis/cluster_util_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Redis::ClusterUtil, feature_category: :scalability do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '.cluster?' do
+ context 'when MultiStore' do
+ let(:redis_cluster) { instance_double(::Redis::Cluster) }
+
+ where(:pri_store, :sec_store, :expected_val) do
+ :cluster | :cluster | true
+ :cluster | :single | true
+ :single | :cluster | true
+ :single | :single | false
+ end
+
+ before do
+ # stub all initialiser steps in Redis::Cluster.new to avoid connecting to a Redis Cluster node
+ allow(::Redis::Cluster).to receive(:new).and_return(redis_cluster)
+ allow(redis_cluster).to receive(:is_a?).with(::Redis::Cluster).and_return(true)
+ allow(redis_cluster).to receive(:id).and_return(1)
+
+ allow(Gitlab::Redis::MultiStore).to receive(:same_redis_store?).and_return(false)
+ skip_feature_flags_yaml_validation
+ skip_default_enabled_yaml_check
+ end
+
+ with_them do
+ it 'returns expected value' do
+ primary_store = pri_store == :cluster ? ::Redis.new(cluster: ['redis://localhost:6000']) : ::Redis.new
+ secondary_store = sec_store == :cluster ? ::Redis.new(cluster: ['redis://localhost:6000']) : ::Redis.new
+ multistore = Gitlab::Redis::MultiStore.new(primary_store, secondary_store, 'teststore')
+ expect(described_class.cluster?(multistore)).to eq(expected_val)
+ end
+ end
+ end
+
+ context 'when is not Redis::Cluster' do
+ it 'returns false' do
+ expect(described_class.cluster?(::Redis.new)).to be_falsey
+ end
+ end
+
+ context 'when is Redis::Cluster' do
+ let(:redis_cluster) { instance_double(::Redis::Cluster) }
+
+ before do
+ # stub all initialiser steps in Redis::Cluster.new to avoid connecting to a Redis Cluster node
+ allow(::Redis::Cluster).to receive(:new).and_return(redis_cluster)
+ allow(redis_cluster).to receive(:is_a?).with(::Redis::Cluster).and_return(true)
+ end
+
+ it 'returns true' do
+ expect(described_class.cluster?(::Redis.new(cluster: ['redis://localhost:6000']))).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/redis/cross_slot_spec.rb b/spec/lib/gitlab/redis/cross_slot_spec.rb
new file mode 100644
index 00000000000..b3eac4357e8
--- /dev/null
+++ b/spec/lib/gitlab/redis/cross_slot_spec.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Redis::CrossSlot, feature_category: :redis do
+ describe '.pipelined' do
+ context 'when using redis client' do
+ before do
+ Gitlab::Redis::Queues.with { |redis| redis.set('a', 1) }
+ end
+
+ it 'performs redis-rb pipelined' do
+ expect(Gitlab::Redis::CrossSlot::Router).not_to receive(:new)
+
+ expect(
+ Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
+ Gitlab::Redis::Queues.with do |redis|
+ described_class::Pipeline.new(redis).pipelined do |p|
+ p.get('a')
+ p.set('b', 1)
+ end
+ end
+ end
+ ).to eq(%w[1 OK])
+ end
+ end
+
+ context 'when using with MultiStore' do
+ let(:multistore) do
+ Gitlab::Redis::MultiStore.new(
+ ::Redis.new(::Gitlab::Redis::SharedState.params),
+ ::Redis.new(::Gitlab::Redis::Sessions.params),
+ 'testing')
+ end
+
+ before do
+ Gitlab::Redis::SharedState.with { |redis| redis.set('a', 1) }
+ Gitlab::Redis::Sessions.with { |redis| redis.set('a', 1) }
+ skip_feature_flags_yaml_validation
+ skip_default_enabled_yaml_check
+ end
+
+ it 'performs multistore pipelined' do
+ expect(Gitlab::Redis::CrossSlot::Router).not_to receive(:new)
+
+ expect(
+ Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
+ described_class::Pipeline.new(multistore).pipelined do |p|
+ p.get('a')
+ p.set('b', 1)
+ end
+ end
+ ).to eq(%w[1 OK])
+ end
+ end
+
+ context 'when using Redis::Cluster' do
+ # Only stub redis client internals since the CI pipeline does not run a Redis Cluster
+ let(:redis) { double(:redis) } # rubocop:disable RSpec/VerifiedDoubles
+ let(:client) { double(:client) } # rubocop:disable RSpec/VerifiedDoubles
+ let(:pipeline) { double(:pipeline) } # rubocop:disable RSpec/VerifiedDoubles
+
+ let(:arguments) { %w[a b c d] }
+
+ subject do
+ described_class::Pipeline.new(redis).pipelined do |p|
+ arguments.each { |key| p.get(key) }
+ end
+ end
+
+ before do
+ allow(redis).to receive(:_client).and_return(client)
+ allow(redis).to receive(:pipelined).and_yield(pipeline)
+ allow(client).to receive(:instance_of?).with(::Redis::Cluster).and_return(true)
+ end
+
+ it 'fan-out and fan-in commands to separate shards' do
+ # simulate fan-out to 3 shards with random order
+ expect(client).to receive(:_find_node_key).exactly(4).times.and_return(3, 2, 1, 3)
+
+ arguments.each do |key|
+ f = double('future') # rubocop:disable RSpec/VerifiedDoubles
+ expect(pipeline).to receive(:get).with(key).and_return(f)
+ expect(f).to receive(:value).and_return(key)
+ end
+
+ expect(subject).to eq(arguments)
+ end
+
+ shared_examples 'fallback on cross-slot' do |redirection|
+ context 'when redis cluster undergoing slot migration' do
+ before do
+ allow(pipeline).to receive(:get).and_raise(::Redis::CommandError.new("#{redirection} 1 127.0.0.1:7001"))
+ end
+
+ it 'logs error and executes sequentially' do
+ expect(client).to receive(:_find_node_key).exactly(4).times.and_return(3, 2, 1, 3)
+ expect(Gitlab::ErrorTracking).to receive(:log_exception).with(an_instance_of(::Redis::CommandError))
+
+ arguments.each do |key|
+ expect(redis).to receive(:get).with(key).and_return(key)
+ end
+
+ subject
+ end
+ end
+ end
+
+ it_behaves_like 'fallback on cross-slot', 'MOVED'
+ it_behaves_like 'fallback on cross-slot', 'ASK'
+
+ context 'when receiving non-MOVED/ASK command errors' do
+ before do
+ allow(pipeline).to receive(:get).and_raise(::Redis::CommandError.new)
+ allow(client).to receive(:_find_node_key).exactly(4).times.and_return(3, 2, 1, 3)
+ end
+
+ it 'raises error' do
+ expect { subject }.to raise_error(::Redis::CommandError)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/redis/multi_store_spec.rb b/spec/lib/gitlab/redis/multi_store_spec.rb
index e45c29a9dd2..75e6e2db6e7 100644
--- a/spec/lib/gitlab/redis/multi_store_spec.rb
+++ b/spec/lib/gitlab/redis/multi_store_spec.rb
@@ -777,6 +777,25 @@ RSpec.describe Gitlab::Redis::MultiStore, feature_category: :redis do
end
end
end
+
+ context 'when either store is a an instance of ::Redis::Cluster' do
+ before do
+ client = double
+ allow(client).to receive(:instance_of?).with(::Redis::Cluster).and_return(true)
+ allow(primary_store).to receive(:_client).and_return(client)
+ end
+
+ it 'calls cross-slot pipeline within multistore' do
+ if name == :pipelined
+ # we intentionally exclude `.and_call_original` since primary_store/secondary_store
+ # may not be running on a proper Redis Cluster.
+ expect(Gitlab::Redis::CrossSlot::Pipeline).to receive(:new).with(primary_store).exactly(:once)
+ expect(Gitlab::Redis::CrossSlot::Pipeline).not_to receive(:new).with(secondary_store)
+ end
+
+ subject
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/repository_cache/preloader_spec.rb b/spec/lib/gitlab/repository_cache/preloader_spec.rb
index e6fb0da6412..44d7d0e1db1 100644
--- a/spec/lib/gitlab/repository_cache/preloader_spec.rb
+++ b/spec/lib/gitlab/repository_cache/preloader_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::RepositoryCache::Preloader, :use_clean_rails_redis_caching,
+RSpec.describe Gitlab::RepositoryCache::Preloader, :use_clean_rails_repository_cache_store_caching,
feature_category: :source_code_management do
let(:projects) { create_list(:project, 2, :repository) }
let(:repositories) { projects.map(&:repository) }
diff --git a/spec/lib/gitlab/repository_hash_cache_spec.rb b/spec/lib/gitlab/repository_hash_cache_spec.rb
index 6b52c315a70..e3cc6ed69fb 100644
--- a/spec/lib/gitlab/repository_hash_cache_spec.rb
+++ b/spec/lib/gitlab/repository_hash_cache_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
-RSpec.describe Gitlab::RepositoryHashCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::RepositoryHashCache, :clean_gitlab_redis_repository_cache, feature_category: :source_code_management do
let_it_be(:project) { create(:project) }
let(:repository) { project.repository }
diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb
index df980b50d03..dc2081fe3a9 100644
--- a/spec/lib/gitlab/repository_set_cache_spec.rb
+++ b/spec/lib/gitlab/repository_set_cache_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
+RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_repository_cache, feature_category: :source_code_management do
let_it_be(:project) { create(:project) }
let(:repository) { project.repository }
@@ -59,8 +59,13 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
it 'writes the value to the cache' do
write_cache
- redis_keys = Gitlab::Redis::Cache.with { |redis| redis.scan(0, match: "*") }.last
- expect(redis_keys).to include("#{gitlab_cache_namespace}:branch_names:#{namespace}:set")
+ cursor, redis_keys = Gitlab::Redis::RepositoryCache.with { |redis| redis.scan(0, match: "*") }
+ while cursor != "0"
+ cursor, keys = Gitlab::Redis::RepositoryCache.with { |redis| redis.scan(cursor, match: "*") }
+ redis_keys << keys
+ end
+
+ expect(redis_keys.flatten).to include("#{gitlab_cache_namespace}:branch_names:#{namespace}:set")
expect(cache.fetch('branch_names')).to contain_exactly('main')
end
@@ -120,7 +125,7 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
context 'when deleting over 1000 keys' do
it 'deletes in batches of 1000' do
Gitlab::Redis::RepositoryCache.with do |redis|
- expect(redis).to receive(:pipelined).twice.and_call_original
+ expect(redis).to receive(:pipelined).at_least(2).and_call_original
end
cache.expire(*(Array.new(1001) { |i| i }))
diff --git a/spec/models/plan_limits_spec.rb b/spec/models/plan_limits_spec.rb
index e1f441405ef..58abd153d28 100644
--- a/spec/models/plan_limits_spec.rb
+++ b/spec/models/plan_limits_spec.rb
@@ -211,11 +211,8 @@ RSpec.describe PlanLimits do
]
end
- # Remove ci_active_pipelines when db column is removed
- # https://gitlab.com/gitlab-org/gitlab/-/issues/408141
let(:columns_with_zero) do
%w[
- ci_active_pipelines
ci_pipeline_size
ci_active_jobs
storage_size_limit
diff --git a/spec/requests/api/graphql/subscriptions/work_item_updated_spec.rb b/spec/requests/api/graphql/subscriptions/work_item_updated_spec.rb
new file mode 100644
index 00000000000..6c0962e7ec0
--- /dev/null
+++ b/spec/requests/api/graphql/subscriptions/work_item_updated_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Subscriptions::WorkItemUpdated, feature_category: :team_planning do
+ include GraphqlHelpers
+ include Graphql::Subscriptions::WorkItems::Helper
+
+ let_it_be(:reporter) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:task) { create(:work_item, :task, project: project) }
+
+ let(:current_user) { nil }
+ let(:subscribe) { work_item_subscription('workItemUpdated', task, current_user) }
+ let(:updated_work_item) { graphql_dig_at(graphql_data(response[:result]), :workItemUpdated) }
+
+ before do
+ stub_const('GitlabSchema', Graphql::Subscriptions::ActionCable::MockGitlabSchema)
+ Graphql::Subscriptions::ActionCable::MockActionCable.clear_mocks
+ project.add_reporter(reporter)
+ end
+
+ subject(:response) do
+ subscription_response do
+ GraphqlTriggers.work_item_updated(task)
+ end
+ end
+
+ context 'when user is unauthorized' do
+ it 'does not receive any data' do
+ expect(response).to be_nil
+ end
+ end
+
+ context 'when user is authorized' do
+ let(:current_user) { reporter }
+
+ it 'receives updated work_item data' do
+ expect(updated_work_item['id']).to eq(task.to_gid.to_s)
+ expect(updated_work_item['iid']).to eq(task.iid.to_s)
+ end
+ end
+end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index f96fbf54f08..ea4e582b239 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -104,6 +104,10 @@ RSpec.describe Issues::UpdateService, :mailer, feature_category: :team_planning
expect(issue.issue_customer_relations_contacts.last.contact).to eq contact
end
+ it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
+ subject(:execute_service) { update_issue(opts) }
+ end
+
context 'when updating milestone' do
before do
update_issue({ milestone_id: nil })
diff --git a/spec/services/work_items/update_service_spec.rb b/spec/services/work_items/update_service_spec.rb
index 6a56e1a72e6..30c16458353 100644
--- a/spec/services/work_items/update_service_spec.rb
+++ b/spec/services/work_items/update_service_spec.rb
@@ -81,6 +81,10 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do
let(:user) { current_user }
subject(:service_action) { update_work_item[:status] }
end
+
+ it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
+ subject(:execute_service) { update_work_item }
+ end
end
context 'when title is not changed' do
@@ -107,6 +111,10 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do
update_work_item
end
+
+ it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
+ subject(:execute_service) { update_work_item }
+ end
end
context 'when decription is changed' do
@@ -117,6 +125,10 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do
update_work_item
end
+
+ it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
+ subject(:execute_service) { update_work_item }
+ end
end
context 'when decription is not changed' do
@@ -219,6 +231,10 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do
expect(work_item.description).to eq('changed')
end
+ it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
+ subject(:execute_service) { update_work_item }
+ end
+
context 'with mentions', :mailer, :sidekiq_might_not_need_inline do
shared_examples 'creates the todo and sends email' do |attribute|
it 'creates a todo and sends email' do
@@ -298,6 +314,10 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do
expect(work_item.work_item_children).to include(child_work_item)
end
+ it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
+ subject(:execute_service) { update_work_item }
+ end
+
context 'when child type is invalid' do
let_it_be(:child_work_item) { create(:work_item, project: project) }
@@ -344,6 +364,10 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do
update_work_item
end
+
+ it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
+ subject(:execute_service) { update_work_item }
+ end
end
context 'when milestone remains unchanged' do
@@ -375,6 +399,10 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do
update_work_item
end
+ it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
+ subject(:execute_service) { update_work_item }
+ end
+
it_behaves_like 'broadcasting issuable labels updates' do
let(:issuable) { work_item }
let(:label_a) { label1 }
diff --git a/spec/support/caching.rb b/spec/support/caching.rb
index b18223523db..119c521f732 100644
--- a/spec/support/caching.rb
+++ b/spec/support/caching.rb
@@ -36,6 +36,19 @@ RSpec.configure do |config|
Rails.cache = original_null_store
end
+ config.around(:each, :use_clean_rails_repository_cache_store_caching) do |example|
+ original_null_store = Rails.cache
+ Rails.cache = Gitlab::Redis::RepositoryCache.cache_store
+
+ redis_repository_cache_cleanup!
+
+ example.run
+
+ redis_repository_cache_cleanup!
+
+ Rails.cache = original_null_store
+ end
+
config.around(:each, :use_sql_query_cache) do |example|
base_models = Gitlab::Database.database_base_models_with_gitlab_shared.values
inner_proc = proc { example.run }
diff --git a/spec/support/helpers/graphql/subscriptions/work_items/helper.rb b/spec/support/helpers/graphql/subscriptions/work_items/helper.rb
new file mode 100644
index 00000000000..9e5817c4134
--- /dev/null
+++ b/spec/support/helpers/graphql/subscriptions/work_items/helper.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module Graphql
+ module Subscriptions
+ module WorkItems
+ module Helper
+ def subscription_response
+ subscription_channel = subscribe
+ yield
+ subscription_channel.mock_broadcasted_messages.first
+ end
+
+ def work_item_subscription(name, work_item, current_user)
+ mock_channel = Graphql::Subscriptions::ActionCable::MockActionCable.get_mock_channel
+
+ query = case name
+ when 'workItemUpdated'
+ work_item_updated_subscription_query(name, work_item)
+ else
+ raise "Subscription query unknown: #{name}"
+ end
+
+ GitlabSchema.execute(query, context: { current_user: current_user, channel: mock_channel })
+
+ mock_channel
+ end
+
+ def note_subscription(name, work_item, current_user)
+ mock_channel = Graphql::Subscriptions::ActionCable::MockActionCable.get_mock_channel
+
+ query = <<~SUBSCRIPTION
+ subscription {
+ #{name}(workItemId: \"#{work_item.to_gid}\") {
+ id
+ iid
+ }
+ }
+ SUBSCRIPTION
+
+ GitlabSchema.execute(query, context: { current_user: current_user, channel: mock_channel })
+
+ mock_channel
+ end
+
+ private
+
+ def work_item_updated_subscription_query(name, work_item)
+ <<~SUBSCRIPTION
+ subscription {
+ #{name}(workItemId: \"#{work_item.to_gid}\") {
+ id
+ iid
+ }
+ }
+ SUBSCRIPTION
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb b/spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb
index 4107bbcb976..849d9ea117e 100644
--- a/spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb
+++ b/spec/support/helpers/models/ci/partitioning_testing/schema_helpers.rb
@@ -67,8 +67,10 @@ module Ci
def drop_test_partition(table_name, connection:)
return unless table_available?(table_name, connection: connection)
+ return unless connection.table_exists?(full_partition_name(table_name))
connection.execute(<<~SQL.squish)
+ ALTER TABLE #{table_name} DETACH PARTITION #{full_partition_name(table_name)};
DROP TABLE IF EXISTS #{full_partition_name(table_name)};
SQL
end
diff --git a/spec/support/shared_contexts/lib/gitlab/database/partitioning/list_partitioning_shared_context.rb b/spec/support/shared_contexts/lib/gitlab/database/partitioning/list_partitioning_shared_context.rb
index e9cd1bdbbf5..3d978a6fde4 100644
--- a/spec/support/shared_contexts/lib/gitlab/database/partitioning/list_partitioning_shared_context.rb
+++ b/spec/support/shared_contexts/lib/gitlab/database/partitioning/list_partitioning_shared_context.rb
@@ -19,7 +19,6 @@ RSpec.shared_context 'with a table structure for converting a table to a list pa
let(:other_referencing_table_name) { '_test_other_referencing_table' }
let(:parent_table_name) { "#{table_name}_parent" }
let(:parent_table_identifier) { "#{connection.current_schema}.#{parent_table_name}" }
- let(:lock_tables) { [] }
let(:model) { define_batchable_model(table_name, connection: connection) }
diff --git a/spec/support/shared_examples/work_items/update_service_shared_examples.rb b/spec/support/shared_examples/work_items/update_service_shared_examples.rb
new file mode 100644
index 00000000000..2d220c0ef58
--- /dev/null
+++ b/spec/support/shared_examples/work_items/update_service_shared_examples.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'update service that triggers GraphQL work_item_updated subscription' do
+ let(:update_subject) do
+ if defined?(work_item)
+ work_item
+ elsif defined?(issue)
+ issue
+ end
+ end
+
+ it 'triggers graphql subscription workItemUpdated' do
+ expect(GraphqlTriggers).to receive(:work_item_updated).with(update_subject).and_call_original
+
+ execute_service
+ end
+end
diff --git a/spec/tasks/cache_rake_spec.rb b/spec/tasks/cache_rake_spec.rb
index 375d01bf2ba..046f8b107f2 100644
--- a/spec/tasks/cache_rake_spec.rb
+++ b/spec/tasks/cache_rake_spec.rb
@@ -53,6 +53,12 @@ RSpec.describe 'clearing redis cache', :clean_gitlab_redis_repository_cache, :cl
end
def redis_keys
- Gitlab::Redis::Cache.with { |redis| redis.scan(0, match: "*") }.last
+ # multiple scans to look across different shards if cache is using a Redis Cluster
+ cursor, scanned_keys = Gitlab::Redis::Cache.with { |redis| redis.scan(0, match: "*") }
+ while cursor != "0"
+ cursor, keys = Gitlab::Redis::Cache.with { |redis| redis.scan(cursor, match: "*") }
+ scanned_keys << keys
+ end
+ scanned_keys.flatten
end
end