diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-20 13:43:29 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-20 13:43:29 +0300 |
commit | 3b1af5cc7ed2666ff18b718ce5d30fa5a2756674 (patch) | |
tree | 3bc4a40e0ee51ec27eabf917c537033c0c5b14d4 /spec/support/shared_examples/lib/gitlab | |
parent | 9bba14be3f2c211bf79e15769cd9b77bc73a13bc (diff) |
Add latest changes from gitlab-org/gitlab@16-1-stable-eev16.1.0-rc42
Diffstat (limited to 'spec/support/shared_examples/lib/gitlab')
5 files changed, 477 insertions, 2 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 diff --git a/spec/support/shared_examples/lib/gitlab/database/foreign_key_validators_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/foreign_key_validators_shared_examples.rb new file mode 100644 index 00000000000..a1e75e4af7e --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/database/foreign_key_validators_shared_examples.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'foreign key validators' do |validator, expected_result| + subject(:result) { validator.new(structure_file, database).execute } + + let(:structure_file_path) { Rails.root.join('spec/fixtures/structure.sql') } + let(:structure_file) { Gitlab::Database::SchemaValidation::StructureSql.new(structure_file_path, schema) } + let(:inconsistency_type) { validator.name.demodulize.underscore } + let(:database_name) { 'main' } + let(:schema) { 'public' } + let(:database_model) { Gitlab::Database.database_base_models[database_name] } + let(:connection) { database_model.connection } + let(:database) { Gitlab::Database::SchemaValidation::Database.new(connection) } + + let(:database_query) do + [ + { + 'schema' => schema, + 'table_name' => 'web_hooks', + 'foreign_key_name' => 'web_hooks_project_id_fkey', + 'foreign_key_definition' => 'FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE' + }, + { + 'schema' => schema, + 'table_name' => 'issues', + 'foreign_key_name' => 'wrong_definition_fk', + 'foreign_key_definition' => 'FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE' + }, + { + 'schema' => schema, + 'table_name' => 'projects', + 'foreign_key_name' => 'extra_fk', + 'foreign_key_definition' => 'FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE CASCADE' + } + ] + end + + before do + allow(connection).to receive(:exec_query).and_return(database_query) + end + + it 'returns trigger inconsistencies' do + expect(result.map(&:object_name)).to match_array(expected_result) + expect(result.map(&:type)).to all(eql inconsistency_type) + end +end diff --git a/spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb index c9300aff3e6..1e03ddac42e 100644 --- a/spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/position_formatters_shared_examples.rb @@ -2,10 +2,9 @@ RSpec.shared_examples "position formatter" do let(:formatter) { described_class.new(attrs) } + let(:key) { [123, 456, 789, Digest::SHA1.hexdigest(formatter.old_path), Digest::SHA1.hexdigest(formatter.new_path), 1, 2] } describe '#key' do - let(:key) { [123, 456, 789, Digest::SHA1.hexdigest(formatter.old_path), Digest::SHA1.hexdigest(formatter.new_path), 1, 2] } - subject { formatter.key } it { is_expected.to eq(key) } diff --git a/spec/support/shared_examples/lib/gitlab/search_archived_filter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_archived_filter_shared_examples.rb new file mode 100644 index 00000000000..7bcefd07fc4 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/search_archived_filter_shared_examples.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'search results filtered by archived' do + context 'when filter not provided (all behavior)' do + let(:filters) { {} } + + it 'returns unarchived results only', :aggregate_failures do + expect(results.objects('projects')).to include unarchived_project + expect(results.objects('projects')).not_to include archived_project + end + end + + context 'when include_archived is true' do + let(:filters) { { include_archived: true } } + + it 'returns archived and unarchived results', :aggregate_failures do + expect(results.objects('projects')).to include unarchived_project + expect(results.objects('projects')).to include archived_project + end + end + + context 'when include_archived filter is false' do + let(:filters) { { include_archived: false } } + + it 'returns unarchived results only', :aggregate_failures do + expect(results.objects('projects')).to include unarchived_project + expect(results.objects('projects')).not_to include archived_project + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/search_labels_filter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_labels_filter_shared_examples.rb new file mode 100644 index 00000000000..b7e408415c3 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/search_labels_filter_shared_examples.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'search results filtered by labels' do + let(:project_label) { create(:label, project: project) } + let!(:issue_1) { create(:labeled_issue, labels: [project_label], project: project, title: 'foo project') } + let!(:unlabeled_issue) { create(:issue, project: project, title: 'foo unlabeled') } + + let(:filters) { { labels: [project_label.id] } } + + before do + ensure_elasticsearch_index! + end + + subject(:issue_results) { results.objects(scope) } + + it 'filters by labels', :sidekiq_inline do + expect(issue_results).to contain_exactly(issue_1) + end +end |