diff options
Diffstat (limited to 'spec/lib/gitlab/database/bulk_update_spec.rb')
-rw-r--r-- | spec/lib/gitlab/database/bulk_update_spec.rb | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/spec/lib/gitlab/database/bulk_update_spec.rb b/spec/lib/gitlab/database/bulk_update_spec.rb new file mode 100644 index 00000000000..f2a7d6e69d8 --- /dev/null +++ b/spec/lib/gitlab/database/bulk_update_spec.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::BulkUpdate do + describe 'error states' do + let(:columns) { %i[title] } + + let_it_be(:mapping) do + create_default(:user) + create_default(:project) + + i_a, i_b = create_list(:issue, 2) + + { + i_a => { title: 'Issue a' }, + i_b => { title: 'Issue b' } + } + end + + it 'does not raise errors on valid inputs' do + expect { described_class.execute(columns, mapping) }.not_to raise_error + end + + it 'expects a non-empty list of column names' do + expect { described_class.execute([], mapping) }.to raise_error(ArgumentError) + end + + it 'expects all columns to be symbols' do + expect { described_class.execute([1], mapping) }.to raise_error(ArgumentError) + end + + it 'expects all columns to be valid columns on the tables' do + expect { described_class.execute([:foo], mapping) }.to raise_error(ArgumentError) + end + + it 'refuses to set ID' do + expect { described_class.execute([:id], mapping) }.to raise_error(ArgumentError) + end + + it 'expects a non-empty mapping' do + expect { described_class.execute(columns, []) }.to raise_error(ArgumentError) + end + + it 'expects all map values to be Hash instances' do + bad_map = mapping.merge(build(:issue) => 2) + + expect { described_class.execute(columns, bad_map) }.to raise_error(ArgumentError) + end + end + + it 'is possible to update all objects in a single query' do + users = create_list(:user, 3) + mapping = users.zip(%w(foo bar baz)).to_h do |u, name| + [u, { username: name, admin: true }] + end + + expect do + described_class.execute(%i[username admin], mapping) + end.not_to exceed_query_limit(1) + + # We have optimistically updated the values + expect(users).to all(be_admin) + expect(users.map(&:username)).to eq(%w(foo bar baz)) + + users.each(&:reset) + + # The values are correct on reset + expect(users).to all(be_admin) + expect(users.map(&:username)).to eq(%w(foo bar baz)) + end + + it 'is possible to update heterogeneous sets' do + create_default(:user) + create_default(:project) + + mr_a = create(:merge_request) + i_a, i_b = create_list(:issue, 2) + + mapping = { + mr_a => { title: 'MR a' }, + i_a => { title: 'Issue a' }, + i_b => { title: 'Issue b' } + } + + expect do + described_class.execute(%i[title], mapping) + end.not_to exceed_query_limit(2) + + expect([mr_a, i_a, i_b].map { |x| x.reset.title }) + .to eq(['MR a', 'Issue a', 'Issue b']) + end + + shared_examples 'basic functionality' do + it 'sets multiple values' do + create_default(:user) + create_default(:project) + + i_a, i_b = create_list(:issue, 2) + + mapping = { + i_a => { title: 'Issue a' }, + i_b => { title: 'Issue b' } + } + + described_class.execute(%i[title], mapping) + + expect([i_a, i_b].map { |x| x.reset.title }) + .to eq(['Issue a', 'Issue b']) + end + end + + include_examples 'basic functionality' + + context 'when prepared statements are configured differently to the normal test environment' do + # rubocop: disable RSpec/LeakyConstantDeclaration + # This cop is disabled because you cannot call establish_connection on + # an anonymous class. + class ActiveRecordBasePreparedStatementsInverted < ActiveRecord::Base + def self.abstract_class? + true # So it gets its own connection + end + end + # rubocop: enable RSpec/LeakyConstantDeclaration + + before_all do + c = ActiveRecord::Base.connection.instance_variable_get(:@config) + inverted = c.merge(prepared_statements: !ActiveRecord::Base.connection.prepared_statements) + ActiveRecordBasePreparedStatementsInverted.establish_connection(inverted) + end + + before do + allow(ActiveRecord::Base).to receive(:connection_specification_name) + .and_return(ActiveRecordBasePreparedStatementsInverted.connection_specification_name) + end + + include_examples 'basic functionality' + end +end |