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:
Diffstat (limited to 'spec/lib/gitlab/database/bulk_update_spec.rb')
-rw-r--r--spec/lib/gitlab/database/bulk_update_spec.rb139
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