# frozen_string_literal: true RSpec.describe ActiveRecord::ConnectionAdapters::PostgreSQLAdapter, :unit do subject(:connection) { ActiveRecord::Base.connection } before do # Sanity check to ensure ActiveRecord is configured to use a PostgreSQL database. expect(connection).to be_a(described_class) # Clear the cached enums before each test to ensure clean tests. connection.clear_enum_cache! end %i[create_enum rename_enum drop_enum add_enum_value rename_enum_value].each do |method| define_method(method) do |*args, &block| connection.send(method, *args, &block).result_status end end # TODO: Investigate possible alternatives as this test is a bit odd. describe '#clear_enum_cache!' do it 'sets @enums to nil' do connection.instance_eval do @enums = { sizes: ['small', 'medium', 'large', 'extra large'] } end connection.clear_enum_cache! expect(connection.instance_eval { @enums }).to be nil end end describe '#enums' do before { connection.execute "CREATE TYPE sizes AS ENUM ('small', 'medium', 'large', 'extra large')" } after { connection.execute 'DROP TYPE IF EXISTS sizes' } subject { connection.enums } it 'retrieves the enums' do expect(subject).to include(sizes: ['small', 'medium', 'large', 'extra large']) end end describe '#enum_execute' do subject { connection.enum_execute('SELECT 1=1') } it 'invokes #execute with the specified arguments' do expect(connection).to receive(:execute).with('SELECT 1=1').once subject end it 'invokes #clear_enum_cache!' do expect(connection).to receive(:clear_enum_cache!).once subject end end describe '#create_enum' do after { connection.execute 'DROP TYPE IF EXISTS sizes' } context 'when called with valid arguments' do subject { create_enum(:sizes, [:small, :medium, :large, 'extra large']) } it 'creates an enum' do expect(subject).to eq(PG::PGRES_COMMAND_OK) expect(connection.enums[:sizes]).to eq(['small', 'medium', 'large', 'extra large']) end end context 'when called with a malformed name' do subject { create_enum('bad enum name', [:small, :medium, :large, 'extra large']) } it 'raises an ArgumentError' do expect { subject }.to raise_exception(ArgumentError) end end context 'when called with malformed values' do subject { create_enum(:sizes, [:small, :medium, :large, 'extra large', 'extra+extra+large']) } it 'raises an ArgumentError' do expect { subject }.to raise_exception(ArgumentError) end end end describe '#rename_enum' do before do connection.execute "CREATE TYPE sizes AS ENUM ('small', 'medium', 'large', 'extra large')" end after do connection.execute 'DROP TYPE IF EXISTS lengths' connection.execute 'DROP TYPE IF EXISTS sizes' end context 'when called with valid arguments' do subject { rename_enum(:sizes, :lengths) } it 'renames the enum' do expect(subject).to eq(PG::PGRES_COMMAND_OK) expect(connection.enums).to have_key(:lengths) expect(connection.enums).not_to have_key(:sizes) end end context 'when called with a non-existent enum name' do subject { rename_enum(:non_existent_enum, :lengths) } it 'raises ActiveRecord::StatementInvalid' do expect { subject }.to raise_exception(ActiveRecord::StatementInvalid) end end context 'when called with a malformed current name' do subject { rename_enum('bad enum name', :lengths) } it 'raises an ArgumentError' do expect { subject }.to raise_exception(ArgumentError) end end context 'when called with a malformed new name' do subject { rename_enum(:sizes, 'bad enum name') } it 'raises an ArgumentError' do expect { subject }.to raise_exception(ArgumentError) end end end describe '#drop_enum' do before { connection.execute "CREATE TYPE sizes AS ENUM ('small', 'medium', 'large', 'extra large')" } after { connection.execute 'DROP TYPE IF EXISTS sizes' } context 'when called with valid arguments' do subject { drop_enum(:sizes) } it 'drops the enum' do expect(subject).to eq(PG::PGRES_COMMAND_OK) expect(connection.enums).not_to have_key(:sizes) end end context 'when called with a non-existent enum name' do subject { drop_enum(:non_existent_enum) } it 'raises ActiveRecord::StatementInvalid' do expect { subject }.to raise_exception(ActiveRecord::StatementInvalid) end end context 'when called with a malformed name' do subject { drop_enum('bad enum name') } it 'raises an ArgumentError' do expect { subject }.to raise_exception(ArgumentError) end end end describe '#add_enum_value' do before { connection.execute "CREATE TYPE sizes AS ENUM ('small', 'large', 'extra large')" } after { connection.execute 'DROP TYPE IF EXISTS sizes' } context 'when called with valid arguments' do subject { add_enum_value(:sizes, 'extra extra large') } it 'appends the value' do expect(subject).to eq(PG::PGRES_COMMAND_OK) expect(connection.enums[:sizes]).to eq(['small', 'large', 'extra large', 'extra extra large']) end context 'with :before' do subject { add_enum_value(:sizes, 'extra small', before: :small) } it 'inserts the value at the correct position' do expect(subject).to eq(PG::PGRES_COMMAND_OK) expect(connection.enums[:sizes]).to eq(['extra small', 'small', 'large', 'extra large']) end end context 'with :after' do subject { add_enum_value(:sizes, :medium, after: :small) } it 'inserts the value at the correct position' do expect(subject).to eq(PG::PGRES_COMMAND_OK) expect(connection.enums[:sizes]).to eq(['small', 'medium', 'large', 'extra large']) end end context 'with :before and :after' do subject { add_enum_value(:sizes, :medium, before: :large, after: :small) } it 'raises an ArgumentError' do expect { subject }.to raise_exception(ArgumentError) end end end context 'when called with a non-existent enum name' do subject { add_enum_value(:non_existent_enum, :new_value) } it 'raises ActiveRecord::StatementInvalid' do expect { subject }.to raise_exception(ActiveRecord::StatementInvalid) end end context 'when called with a malformed name' do subject { add_enum_value('bad enum name', :new_value) } it 'raises an ArgumentError' do expect { subject }.to raise_exception(ArgumentError) end end end describe '#rename_enum_value' do before { connection.execute "CREATE TYPE sizes AS ENUM ('small', 'medium', 'large', 'extra large')" } after { connection.execute 'DROP TYPE IF EXISTS sizes' } context 'when called with valid arguments' do subject { rename_enum_value(:sizes, 'extra large', :extra_large) } it 'renames the value' do expect(subject).to eq(PG::PGRES_COMMAND_OK) expect(connection.enums[:sizes]).to eq(%w[small medium large extra_large]) end context 'when using PostgreSQL < 10.0' do before do allow(connection).to receive(:postgresql_version).and_return(99_999) end it 'raises a NotImplementedError' do expect { subject }.to raise_exception(NotImplementedError) end end end context 'when called with a non-existent enum name' do subject { rename_enum_value(:non_existent_enum, :previous_value, :new_value) } it 'raises ActiveRecord::StatementInvalid' do expect { subject }.to raise_exception(ActiveRecord::StatementInvalid) end end context 'when called with a non-existent enum value' do subject { rename_enum_value(:sizes, 'extra extra large', :extra_extra_large) } it 'raises ActiveRecord::StatementInvalid' do expect { subject }.to raise_exception(ActiveRecord::StatementInvalid) end end context 'when called with a malformed name' do subject { rename_enum_value('bad enum name', 'extra large', :extra_large) } it 'raises an ArgumentError' do expect { subject }.to raise_exception(ArgumentError) end end context 'when called with a malformed current value' do subject { rename_enum_value(:sizes, 'extra-large', :extra_large) } it 'raises an ArgumentError' do expect { subject }.to raise_exception(ArgumentError) end end context 'when called with a malformed new value' do subject { rename_enum_value(:sizes, 'extra large', 'extra-large') } it 'raises an ArgumentError' do expect { subject }.to raise_exception(ArgumentError) end end context 'when called with an equivalent current value and new value' do subject { rename_enum_value(:sizes, 'extra large', 'extra large') } it 'raises ActiveRecord::StatementInvalid' do expect { subject }.to raise_exception(ActiveRecord::StatementInvalid) end end context 'when called with a new value matching an existing value' do subject { rename_enum_value(:sizes, 'extra large', :large) } it 'raises ActiveRecord::StatementInvalid' do expect { subject }.to raise_exception(ActiveRecord::StatementInvalid) end end end end