module ThinkingSphinx
  module ActiveRecord
    class Attribute; end
  end
end

require 'thinking_sphinx/errors'
require 'thinking_sphinx/active_record/attribute/type'

describe ThinkingSphinx::ActiveRecord::Attribute::Type do
  let(:type) {
    ThinkingSphinx::ActiveRecord::Attribute::Type.new attribute, model }
  let(:attribute) { double('attribute', :columns => [column], :options => {}) }
  let(:model)     { double('model', :columns => [db_column]) }
  let(:column)    { double('column', :__name => :created_at, :string? => false,
    :__stack => []) }
  let(:db_column) { double('column', :name => 'created_at',
    :type => :integer) }

  describe '#multi?' do
    let(:association) { double('association', :klass => double) }

    before :each do
      column.__stack << :foo

      model.stub :reflect_on_association => association
    end

    it "returns true if there are has_many associations" do
      association.stub :macro => :has_many

      type.should be_multi
    end

    it "returns true if there are has_and_belongs_to_many associations" do
      association.stub :macro => :has_and_belongs_to_many

      type.should be_multi
    end

    it "returns false if there are no associations" do
      column.__stack.clear

      type.should_not be_multi
    end

    it "returns false if there are only belongs_to associations" do
      association.stub :macro => :belongs_to

      type.should_not be_multi
    end

    it "returns false if there are only has_one associations" do
      association.stub :macro => :has_one

      type.should_not be_multi
    end

    it "returns true if deeper associations have many" do
      column.__stack << :bar
      deep_association = double(:klass => double, :macro => :has_many)
      association.stub :macro => :belongs_to,
        :klass => double(:reflect_on_association => deep_association)

      type.should be_multi
    end

    it "respects the provided setting" do
      attribute.options[:multi] = true

      type.should be_multi
    end
  end

  describe '#type' do
    it "returns the type option provided" do
      attribute.options[:type] = :datetime

      type.type.should == :datetime
    end

    it "detects integer types from the database" do
      db_column.stub!(:type => :integer, :sql_type => 'integer(11)')

      type.type.should == :integer
    end

    it "detects boolean types from the database" do
      db_column.stub!(:type => :boolean)

      type.type.should == :boolean
    end

    it "detects datetime types from the database as timestamps" do
      db_column.stub!(:type => :datetime)

      type.type.should == :timestamp
    end

    it "detects date types from the database as timestamps" do
      db_column.stub!(:type => :date)

      type.type.should == :timestamp
    end

    it "detects string types from the database" do
      db_column.stub!(:type => :string)

      type.type.should == :string
    end

    it "detects text types from the database as strings" do
      db_column.stub!(:type => :text)

      type.type.should == :string
    end

    it "detects float types from the database" do
      db_column.stub!(:type => :float)

      type.type.should == :float
    end

    it "detects decimal types from the database as floats" do
      db_column.stub!(:type => :decimal)

      type.type.should == :float
    end

    it "detects big ints as big ints" do
      db_column.stub :type => :bigint

      type.type.should == :bigint
    end

    it "detects large integers as big ints" do
      db_column.stub :type => :integer, :sql_type => 'bigint(20)'

      type.type.should == :bigint
    end

    it "detects JSON" do
      db_column.stub :type => :json

      type.type.should == :json
    end

    it "respects provided type setting" do
      attribute.options[:type] = :timestamp

      type.type.should == :timestamp
    end

    it 'raises an error if the database column does not exist' do
      model.columns.clear

      expect { type.type }.to raise_error(ThinkingSphinx::MissingColumnError)
    end
  end
end
