require 'spec_helper'
require 'schema_expectations/active_record/column_reflector'

module SchemaExpectations
  module ActiveRecord
    describe ColumnReflector, :active_record do
      subject(:column_reflector) { ColumnReflector.new(Record) }

      context '#without_present_default' do
        specify 'filters default values' do
          create_table :records do |t|
            t.integer :integer_default, default: 0
            t.string :string_default, default: 'test'
            t.string :empty_default, default: ''
            t.string :null_default, default: nil
            t.string :no_default

            t.timestamps null: false
          end
          stub_const('Record', Class.new(::ActiveRecord::Base))

          expect(column_reflector.column_names).
            to eq %i(id integer_default string_default empty_default null_default no_default created_at updated_at)

          expect(column_reflector.without_present_default.column_names).
            to eq %i(empty_default null_default no_default)
        end

        context 'default functions', :postgresql, active_record_version: '>= 4.0' do
          specify 'are filtered' do
            create_table :records do |t|
              t.uuid :uuid_default, default: 'uuid_generate_v4()'
              t.string :no_default
            end
            execute <<-SQL
              -- not possible via create_table syntax right now
              ALTER TABLE records
                ADD COLUMN function_default float DEFAULT random();
            SQL
            stub_const('Record', Class.new(::ActiveRecord::Base))

            expect(Record.columns_hash['function_default'].default).to be_nil
            expect(Record.columns_hash['uuid_default'].default).to be_nil
            expect(Record.columns_hash['function_default'].default_function).to_not be_nil
            expect(Record.columns_hash['uuid_default'].default_function).to_not be_nil

            expect(column_reflector.column_names).
              to eq %i(id uuid_default no_default function_default)

            expect(column_reflector.without_present_default.column_names).
              to eq %i(no_default)
          end

          specify 'logs if it encounters an error trying to run default function' do
            create_table :records
            execute <<-SQL
              CREATE FUNCTION raise_error() RETURNS varchar
                AS $BODY$
                  BEGIN
                    RAISE 'test exception';
                  END;
                $BODY$
                LANGUAGE plpgsql;

              -- not possible via create_table syntax right now
              ALTER TABLE records
                ADD COLUMN function_default varchar DEFAULT raise_error();
            SQL
            stub_const('Record', Class.new(::ActiveRecord::Base))

            expect(Record.columns_hash['function_default'].default).to be_nil
            expect(Record.columns_hash['function_default'].default_function).to_not be_nil

            expect(column_reflector.column_names).
              to eq %i(id function_default)

            expect(SchemaExpectations.error_logger).to receive(:error).once do |message|
              expect(message).to include 'SchemaExpectations: encountered error running SELECT raise_error()'
              expect(message).to include 'PG::RaiseException: ERROR:  test exception'
              expect(message).to include ': SELECT raise_error()'
            end
            expect(column_reflector.without_present_default.column_names).
              to eq %i(function_default)
          end
        end
      end

      specify '#not_null' do
        create_table :records do |t|
          t.string :not_null, null: false
          t.string :nullable
        end
        stub_const('Record', Class.new(::ActiveRecord::Base))

        expect(column_reflector.column_names).to eq %i(id not_null nullable)
        expect(column_reflector.not_null.column_names).to eq %i(id not_null)
      end

      context '#for_attributes' do
        before do
          create_table :records do |t|
            t.string :record_id
            t.string :record_type
            t.string :other
          end

          stub_const('Record', Class.new(::ActiveRecord::Base))
        end

        specify 'without associations' do
          expect(column_reflector.column_names).to eq %i(id record_id record_type other)
          expect(column_reflector.for_attributes(:missing).column_names).to be_empty
          expect(column_reflector.for_attributes(:record).column_names).to be_empty
          expect(column_reflector.for_attributes(:record_id).column_names).to eq %i(record_id)
          expect(column_reflector.for_attributes(:record_type).column_names).to eq %i(record_type)
          expect(column_reflector.for_attributes(:other).column_names).to eq %i(other)
          expect(column_reflector.for_attributes(*%i(record missing other)).column_names).to eq %i(other)
        end

        specify 'belongs_to polymorphic' do
          Record.instance_eval do
            belongs_to :record, polymorphic: true
          end

          expect(column_reflector.column_names).to eq %i(id record_id record_type other)
          expect(column_reflector.for_attributes(:missing).column_names).to be_empty
          expect(column_reflector.for_attributes(:record).column_names).to eq %i(record_id record_type)
          expect(column_reflector.for_attributes(:record_id).column_names).to eq %i(record_id)
          expect(column_reflector.for_attributes(:record_type).column_names).to eq %i(record_type)
          expect(column_reflector.for_attributes(:other).column_names).to eq %i(other)
          expect(column_reflector.for_attributes(*%i(record missing other)).column_names).to eq %i(record_id record_type other)
        end

        specify 'belongs_to' do
          Record.instance_eval do
            belongs_to :record
          end

          expect(column_reflector.column_names).to eq %i(id record_id record_type other)
          expect(column_reflector.for_attributes(:missing).column_names).to be_empty
          expect(column_reflector.for_attributes(:record).column_names).to eq %i(record_id)
          expect(column_reflector.for_attributes(:record_id).column_names).to eq %i(record_id)
          expect(column_reflector.for_attributes(:record_type).column_names).to eq %i(record_type)
          expect(column_reflector.for_attributes(:other).column_names).to eq %i(other)
          expect(column_reflector.for_attributes(*%i(record missing other)).column_names).to eq %i(record_id other)
        end
      end

      specify '#unique_scopes' do
        create_table :records do |t|
          t.string :a
          t.string :b
          t.string :c
          t.string :d
          t.string :not_unique
          t.string :other
        end

        add_index :records, :not_unique
        add_index :records, :a, unique: true
        add_index :records, %i(a b), unique: true
        add_index :records, %i(c a b), unique: true
        add_index :records, %i(d b), unique: true
        add_index :records, %i(a not_unique)

        stub_const('Record', Class.new(::ActiveRecord::Base))

        expect(column_reflector.unique_scopes).to eq [
          %i(a),
          %i(a b),
          %i(a b c),
          %i(b d)
        ]
      end
    end
  end
end
