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
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-06-20 13:43:29 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-20 13:43:29 +0300
commit3b1af5cc7ed2666ff18b718ce5d30fa5a2756674 (patch)
tree3bc4a40e0ee51ec27eabf917c537033c0c5b14d4 /spec/support/shared_examples/lib/gitlab/cache
parent9bba14be3f2c211bf79e15769cd9b77bc73a13bc (diff)
Add latest changes from gitlab-org/gitlab@16-1-stable-eev16.1.0-rc42
Diffstat (limited to 'spec/support/shared_examples/lib/gitlab/cache')
-rw-r--r--spec/support/shared_examples/lib/gitlab/cache/json_cache_shared_examples.rb379
1 files changed, 379 insertions, 0 deletions
diff --git a/spec/support/shared_examples/lib/gitlab/cache/json_cache_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cache/json_cache_shared_examples.rb
new file mode 100644
index 00000000000..0472bb87e62
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/cache/json_cache_shared_examples.rb
@@ -0,0 +1,379 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'Json Cache class' do
+ describe '#read' do
+ it 'returns the cached value when there is data in the cache with the given key' do
+ allow(backend).to receive(:read).with(expanded_key).and_return(json_value(true))
+
+ expect(cache.read(key)).to eq(true)
+ end
+
+ it 'returns nil when there is no data in the cache with the given key' do
+ allow(backend).to receive(:read).with(expanded_key).and_return(nil)
+
+ expect(Gitlab::Json).not_to receive(:parse)
+ expect(cache.read(key)).to be_nil
+ end
+
+ it 'parses the cached value' do
+ allow(backend).to receive(:read).with(expanded_key).and_return(json_value(broadcast_message))
+
+ expect(cache.read(key, BroadcastMessage)).to eq(broadcast_message)
+ end
+
+ it 'returns nil when klass is nil' do
+ allow(backend).to receive(:read).with(expanded_key).and_return(json_value(broadcast_message))
+
+ expect(cache.read(key)).to be_nil
+ end
+
+ it 'gracefully handles an empty hash' do
+ allow(backend).to receive(:read).with(expanded_key).and_return(json_value({}))
+
+ expect(cache.read(key, BroadcastMessage)).to be_a(BroadcastMessage)
+ end
+
+ context 'when the cached value is a JSON true value' do
+ it 'parses the cached value' do
+ allow(backend).to receive(:read).with(expanded_key).and_return(json_value(true))
+
+ expect(cache.read(key, BroadcastMessage)).to eq(true)
+ end
+ end
+
+ context 'when the cached value is a JSON false value' do
+ it 'parses the cached value' do
+ allow(backend).to receive(:read).with(expanded_key).and_return(json_value(false))
+
+ expect(cache.read(key, BroadcastMessage)).to eq(false)
+ end
+ end
+
+ context 'when the cached value is a hash' do
+ it 'gracefully handles bad cached entry' do
+ allow(backend).to receive(:read).with(expanded_key).and_return('{')
+
+ expect(cache.read(key, BroadcastMessage)).to be_nil
+ end
+
+ it 'gracefully handles unknown attributes' do
+ read_value = json_value(broadcast_message.attributes.merge(unknown_attribute: 1))
+ allow(backend).to receive(:read).with(expanded_key).and_return(read_value)
+
+ expect(cache.read(key, BroadcastMessage)).to be_nil
+ end
+
+ it 'gracefully handles excluded fields from attributes during serialization' do
+ read_value = json_value(broadcast_message.attributes.except("message_html"))
+ allow(backend).to receive(:read).with(expanded_key).and_return(read_value)
+
+ result = cache.read(key, BroadcastMessage)
+
+ BroadcastMessage.cached_markdown_fields.html_fields.each do |field|
+ expect(result.public_send(field)).to be_nil
+ end
+ end
+ end
+
+ context 'when the cached value is an array' do
+ it 'parses the cached value' do
+ allow(backend).to receive(:read).with(expanded_key).and_return(json_value([broadcast_message]))
+
+ expect(cache.read(key, BroadcastMessage)).to eq([broadcast_message])
+ end
+
+ it 'returns an empty array when klass is nil' do
+ allow(backend).to receive(:read).with(expanded_key).and_return(json_value([broadcast_message]))
+
+ expect(cache.read(key)).to eq([])
+ end
+
+ it 'gracefully handles bad cached entry' do
+ allow(backend).to receive(:read).with(expanded_key).and_return('[')
+
+ expect(cache.read(key, BroadcastMessage)).to be_nil
+ end
+
+ it 'gracefully handles an empty array' do
+ allow(backend).to receive(:read).with(expanded_key).and_return(json_value([]))
+
+ expect(cache.read(key, BroadcastMessage)).to eq([])
+ end
+
+ it 'gracefully handles items with unknown attributes' do
+ read_value = json_value([{ unknown_attribute: 1 }, broadcast_message.attributes])
+ allow(backend).to receive(:read).with(expanded_key).and_return(read_value)
+
+ expect(cache.read(key, BroadcastMessage)).to eq([broadcast_message])
+ end
+ end
+ end
+
+ describe '#write' do
+ it 'writes value to the cache with the given key' do
+ cache.write(key, true)
+
+ expect(backend).to have_received(:write).with(expanded_key, json_value(true), nil)
+ end
+
+ it 'writes a string containing a JSON representation of the value to the cache' do
+ cache.write(key, broadcast_message)
+
+ expect(backend).to have_received(:write).with(expanded_key, json_value(broadcast_message), nil)
+ end
+
+ it 'passes options the underlying cache implementation' do
+ cache.write(key, true, expires_in: 15.seconds)
+
+ expect(backend).to have_received(:write).with(expanded_key, json_value(true), expires_in: 15.seconds)
+ end
+
+ it 'passes options the underlying cache implementation when options is empty' do
+ cache.write(key, true, {})
+
+ expect(backend).to have_received(:write).with(expanded_key, json_value(true), {})
+ end
+
+ it 'passes options the underlying cache implementation when options is nil' do
+ cache.write(key, true, nil)
+
+ expect(backend).to have_received(:write).with(expanded_key, json_value(true), nil)
+ end
+ end
+
+ # rubocop:disable Style/RedundantFetchBlock
+ describe '#fetch', :use_clean_rails_memory_store_caching do
+ let(:backend) { Rails.cache }
+
+ it 'requires a block' do
+ expect { cache.fetch(key) }.to raise_error(LocalJumpError)
+ end
+
+ it 'passes options the underlying cache implementation' do
+ expect(backend).to receive(:write).with(expanded_key, json_value(true), { expires_in: 15.seconds })
+
+ cache.fetch(key, { expires_in: 15.seconds }) { true }
+ end
+
+ context 'when the given key does not exist in the cache' do
+ context 'when the result of the block is truthy' do
+ it 'returns the result of the block' do
+ result = cache.fetch(key) { true }
+
+ expect(result).to eq(true)
+ end
+
+ it 'caches the value' do
+ expect(backend).to receive(:write).with(expanded_key, json_value(true), {})
+
+ cache.fetch(key) { true }
+ end
+ end
+
+ context 'when the result of the block is false' do
+ it 'returns the result of the block' do
+ result = cache.fetch(key) { false }
+
+ expect(result).to eq(false)
+ end
+
+ it 'caches the value' do
+ expect(backend).to receive(:write).with(expanded_key, json_value(false), {})
+
+ cache.fetch(key) { false }
+ end
+ end
+
+ context 'when the result of the block is nil' do
+ it 'returns the result of the block' do
+ result = cache.fetch(key) { nil }
+
+ expect(result).to eq(nil)
+ end
+
+ it 'caches the value' do
+ expect(backend).to receive(:write).with(expanded_key, json_value(nil), {})
+
+ cache.fetch(key) { nil }
+ end
+ end
+ end
+
+ context 'when the given key exists in the cache' do
+ context 'when the cached value is a hash' do
+ before do
+ backend.write(expanded_key, json_value(broadcast_message))
+ end
+
+ it 'parses the cached value' do
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to eq(broadcast_message)
+ end
+
+ it 'decodes enums correctly' do
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result.broadcast_type).to eq(broadcast_message.broadcast_type)
+ end
+
+ context 'when the cached value is an instance of ActiveRecord::Base' do
+ it 'returns a persisted record when id is set' do
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to be_persisted
+ end
+
+ it 'returns a new record when id is nil' do
+ backend.write(expanded_key, json_value(build(:broadcast_message)))
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to be_new_record
+ end
+
+ it 'returns a new record when id is missing' do
+ backend.write(expanded_key, json_value(build(:broadcast_message).attributes.except('id')))
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to be_new_record
+ end
+
+ it 'gracefully handles bad cached entry' do
+ allow(backend).to receive(:read).with(expanded_key).and_return('{')
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to eq 'block result'
+ end
+
+ it 'gracefully handles an empty hash' do
+ allow(backend).to receive(:read).with(expanded_key).and_return(json_value({}))
+
+ expect(cache.fetch(key, as: BroadcastMessage)).to be_a(BroadcastMessage)
+ end
+
+ it 'gracefully handles unknown attributes' do
+ read_value = json_value(broadcast_message.attributes.merge(unknown_attribute: 1))
+ allow(backend).to receive(:read).with(expanded_key).and_return(read_value)
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to eq 'block result'
+ end
+
+ it 'gracefully handles excluded fields from attributes during serialization' do
+ read_value = json_value(broadcast_message.attributes.except("message_html"))
+ allow(backend).to receive(:read).with(expanded_key).and_return(read_value)
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ BroadcastMessage.cached_markdown_fields.html_fields.each do |field|
+ expect(result.public_send(field)).to be_nil
+ end
+ end
+ end
+
+ it 'returns the result of the block when `as` option is nil' do
+ result = cache.fetch(key, as: nil) { 'block result' }
+
+ expect(result).to eq('block result')
+ end
+
+ it 'returns the result of the block when `as` option is missing' do
+ result = cache.fetch(key) { 'block result' }
+
+ expect(result).to eq('block result')
+ end
+ end
+
+ context 'when the cached value is a array' do
+ before do
+ backend.write(expanded_key, json_value([broadcast_message]))
+ end
+
+ it 'parses the cached value' do
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to eq([broadcast_message])
+ end
+
+ it 'returns an empty array when `as` option is nil' do
+ result = cache.fetch(key, as: nil) { 'block result' }
+
+ expect(result).to eq([])
+ end
+
+ it 'returns an empty array when `as` option is not provided' do
+ result = cache.fetch(key) { 'block result' }
+
+ expect(result).to eq([])
+ end
+ end
+
+ context 'when the cached value is true' do
+ before do
+ backend.write(expanded_key, json_value(true))
+ end
+
+ it 'returns the cached value' do
+ result = cache.fetch(key) { 'block result' }
+
+ expect(result).to eq(true)
+ end
+
+ it 'does not execute the block' do
+ expect { |block| cache.fetch(key, &block) }.not_to yield_control
+ end
+
+ it 'does not write to the cache' do
+ expect(backend).not_to receive(:write)
+
+ cache.fetch(key) { 'block result' }
+ end
+ end
+
+ context 'when the cached value is false' do
+ before do
+ backend.write(expanded_key, json_value(false))
+ end
+
+ it 'returns the cached value' do
+ result = cache.fetch(key) { 'block result' }
+
+ expect(result).to eq(false)
+ end
+
+ it 'does not execute the block' do
+ expect { |block| cache.fetch(key, &block) }.not_to yield_control
+ end
+
+ it 'does not write to the cache' do
+ expect(backend).not_to receive(:write)
+
+ cache.fetch(key) { 'block result' }
+ end
+ end
+
+ context 'when the cached value is nil' do
+ before do
+ backend.write(expanded_key, json_value(nil))
+ end
+
+ it 'returns the result of the block' do
+ result = cache.fetch(key) { 'block result' }
+
+ expect(result).to eq('block result')
+ end
+
+ it 'writes the result of the block to the cache' do
+ expect(backend).to receive(:write).with(expanded_key, json_value('block result'), {})
+
+ cache.fetch(key) { 'block result' }
+ end
+ end
+ end
+ end
+ # rubocop:enable Style/RedundantFetchBlock
+end