# frozen_string_literal: true require 'spec_helper' RSpec.describe Gitlab::Cache::Client, feature_category: :source_code_management do subject(:client) { described_class.new(metadata, backend: backend) } let(:backend) { Rails.cache } let(:metadata) do Gitlab::Cache::Metadata.new( cache_identifier: cache_identifier, feature_category: feature_category, backing_resource: backing_resource ) end let(:cache_identifier) { 'MyClass#cache' } let(:feature_category) { :source_code_management } let(:backing_resource) { :cpu } let(:metadata_mock) do Gitlab::Cache::Metadata.new( cache_identifier: cache_identifier, feature_category: feature_category ) end let(:metrics_mock) { Gitlab::Cache::Metrics.new(metadata_mock) } describe '.build_with_metadata' do it 'builds a cache client with metrics support' do attributes = { cache_identifier: cache_identifier, feature_category: feature_category, backing_resource: backing_resource } instance = described_class.build_with_metadata(**attributes) expect(instance).to be_a(described_class) expect(instance.metadata).to have_attributes(**attributes) end end describe 'Methods', :use_clean_rails_memory_store_caching do let(:expected_key) { 'key' } before do allow(Gitlab::Cache::Metrics).to receive(:new).and_return(metrics_mock) end describe '#read' do context 'when key does not exist' do it 'returns nil' do expect(client.read('key')).to be_nil end it 'increments cache miss' do expect(metrics_mock).to receive(:increment_cache_miss) client.read('key') end end context 'when key exists' do before do backend.write(expected_key, 'value') end it 'returns key value' do expect(client.read('key')).to eq('value') end it 'increments cache hit' do expect(metrics_mock).to receive(:increment_cache_hit) client.read('key') end end end describe '#write' do it 'calls backend "#write" method with the expected key' do expect(backend).to receive(:write).with(expected_key, 'value') client.write('key', 'value') end end describe '#exist?' do it 'calls backend "#exist?" method with the expected key' do expect(backend).to receive(:exist?).with(expected_key) client.exist?('key') end end describe '#delete' do it 'calls backend "#delete" method with the expected key' do expect(backend).to receive(:delete).with(expected_key) client.delete('key') end end # rubocop:disable Style/RedundantFetchBlock describe '#fetch' do it 'creates key in the specific format' do client.fetch('key') { 'value' } expect(backend.fetch(expected_key)).to eq('value') end it 'yields the block once' do expect { |b| client.fetch('key', &b) }.to yield_control.once end context 'when key already exists' do before do backend.write(expected_key, 'value') end it 'does not redefine the value' do expect(client.fetch('key') { 'new-value' }).to eq('value') end it 'increments a cache hit' do expect(metrics_mock).to receive(:increment_cache_hit) client.fetch('key') end it 'does not measure the cache generation time' do expect(metrics_mock).not_to receive(:observe_cache_generation) client.fetch('key') { 'new-value' } end end context 'when key does not exist' do it 'caches the key' do expect(client.fetch('key') { 'value' }).to eq('value') expect(client.fetch('key')).to eq('value') end it 'increments a cache miss' do expect(metrics_mock).to receive(:increment_cache_miss) client.fetch('key') end it 'measures the cache generation time' do expect(metrics_mock).to receive(:observe_cache_generation) client.fetch('key') { 'value' } end end end end # rubocop:enable Style/RedundantFetchBlock end