# frozen_string_literal: true require 'spec_helper' RSpec.describe Gitlab::SafeRequestLoader, :aggregate_failures do let(:resource_key) { '_key_' } let(:resource_ids) { [] } let(:args) { { resource_key: resource_key, resource_ids: resource_ids } } let(:block) { proc { {} } } describe '.execute', :request_store do let(:resource_data) { { 'foo' => 'bar' } } before do Gitlab::SafeRequestStore[resource_key] = resource_data end subject(:execute_instance) { described_class.execute(**args, &block) } it 'gets data from the store and returns it' do expect(execute_instance.keys).to contain_exactly(*resource_data.keys) expect(execute_instance).to match(a_hash_including(resource_data)) expect_store_to_be_updated end end describe '#execute' do subject(:execute_instance) { described_class.new(**args).execute(&block) } context 'without a block' do let(:block) { nil } it 'raises an error' do expect { execute_instance }.to raise_error(ArgumentError, 'Block is mandatory') end end context 'when a resource_id is nil' do let(:block) { proc { {} } } let(:resource_ids) { [nil] } it 'contains resource_data with nil key' do expect(execute_instance.keys).to contain_exactly(nil) expect(execute_instance).to match(a_hash_including(nil => nil)) end end context 'with SafeRequestStore considerations' do let(:resource_data) { { 'foo' => 'bar' } } before do Gitlab::SafeRequestStore[resource_key] = resource_data end context 'when request store is active', :request_store do it 'gets data from the store' do expect(execute_instance.keys).to contain_exactly(*resource_data.keys) expect(execute_instance).to match(a_hash_including(resource_data)) expect_store_to_be_updated end context 'with already loaded resource_ids', :request_store do let(:resource_key) { 'foo_data' } let(:existing_resource_data) { { 'foo' => 'zoo' } } let(:block) { proc { { 'foo' => 'bar' } } } let(:resource_ids) { ['foo'] } before do Gitlab::SafeRequestStore[resource_key] = existing_resource_data end it 'does not re-fetch data if resource_id already exists' do expect(execute_instance.keys).to contain_exactly(*resource_ids) expect(execute_instance).to match(a_hash_including(existing_resource_data)) expect_store_to_be_updated end context 'with mixture of new and existing resource_ids' do let(:existing_resource_data) { { 'foo' => 'bar' } } let(:resource_ids) { %w[foo bar] } context 'when block does not filter for only the missing resource_ids' do let(:block) { proc { { 'foo' => 'zoo', 'bar' => 'foo' } } } it 'overwrites existing keyed data with results from the block' do expect(execute_instance.keys).to contain_exactly(*resource_ids) expect(execute_instance).to match(a_hash_including(block.call)) expect_store_to_be_updated end end context 'when passing the missing resource_ids to a block that filters for them' do let(:block) { proc { |rids| { 'foo' => 'zoo', 'bar' => 'foo' }.select { |k, _v| rids.include?(k) } } } it 'only updates resource_data with keyed items that did not exist' do expect(execute_instance.keys).to contain_exactly(*resource_ids) expect(execute_instance).to match(a_hash_including({ 'foo' => 'bar', 'bar' => 'foo' })) expect_store_to_be_updated end end context 'with default_value for resource_ids that did not exist in the results' do context 'when default_value is provided' do let(:args) { { resource_key: resource_key, resource_ids: resource_ids, default_value: '_value_' } } it 'populates a default value' do expect(execute_instance.keys).to contain_exactly(*resource_ids) expect(execute_instance).to match(a_hash_including({ 'foo' => 'bar', 'bar' => '_value_' })) expect_store_to_be_updated end end context 'when default_value is not provided' do it 'populates a default_value of nil' do expect(execute_instance.keys).to contain_exactly(*resource_ids) expect(execute_instance).to match(a_hash_including({ 'foo' => 'bar', 'bar' => nil })) expect_store_to_be_updated end end end end end end context 'when request store is not active' do let(:block) { proc { { 'foo' => 'bar' } } } let(:resource_ids) { ['foo'] } it 'has no data added from the store' do expect(execute_instance).to eq(block.call) end context 'with mixture of new and existing resource_ids' do let(:resource_ids) { %w[foo bar] } context 'when block does not filter out existing resource_data keys' do let(:block) { proc { { 'foo' => 'zoo', 'bar' => 'foo' } } } it 'overwrites existing keyed data with results from the block' do expect(execute_instance.keys).to contain_exactly(*resource_ids) expect(execute_instance).to match(a_hash_including(block.call)) end end context 'when passing the missing resource_ids to a block that filters for them' do let(:block) { proc { |rids| { 'foo' => 'zoo', 'bar' => 'foo' }.select { |k, _v| rids.include?(k) } } } it 'only updates resource_data with keyed items that did not exist' do expect(execute_instance.keys).to contain_exactly(*resource_ids) expect(execute_instance).to match(a_hash_including({ 'foo' => 'zoo', 'bar' => 'foo' })) end end context 'with default_value for resource_ids that did not exist in the results' do context 'when default_value is provided' do let(:args) { { resource_key: resource_key, resource_ids: resource_ids, default_value: '_value_' } } it 'populates a default value' do expect(execute_instance.keys).to contain_exactly(*resource_ids) expect(execute_instance).to match(a_hash_including({ 'foo' => 'bar', 'bar' => '_value_' })) end end context 'when default_value is not provided' do it 'populates a default_value of nil' do expect(execute_instance.keys).to contain_exactly(*resource_ids) expect(execute_instance).to match(a_hash_including({ 'foo' => 'bar', 'bar' => nil })) end end end end end end end def expect_store_to_be_updated expect(execute_instance).to match(a_hash_including(Gitlab::SafeRequestStore[resource_key])) expect(execute_instance.keys).to contain_exactly(*Gitlab::SafeRequestStore[resource_key].keys) end end