136 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			136 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Ruby
		
	
	
	
# 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
 | 
						|
    before do
 | 
						|
      klass = Class.new(ActiveRecord::Base) do
 | 
						|
        def self.abstract_class?
 | 
						|
          true # So it gets its own connection
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      stub_const('ActiveRecordBasePreparedStatementsInverted', klass)
 | 
						|
 | 
						|
      c = ActiveRecord::Base.connection.instance_variable_get(:@config)
 | 
						|
      inverted = c.merge(prepared_statements: !ActiveRecord::Base.connection.prepared_statements)
 | 
						|
      ActiveRecordBasePreparedStatementsInverted.establish_connection(inverted)
 | 
						|
 | 
						|
      allow(ActiveRecord::Base).to receive(:connection_specification_name)
 | 
						|
        .and_return(ActiveRecordBasePreparedStatementsInverted.connection_specification_name)
 | 
						|
    end
 | 
						|
 | 
						|
    include_examples 'basic functionality'
 | 
						|
  end
 | 
						|
end
 |