require 'test_helper'

class Address
  include MongoMapper::EmbeddedDocument

  key :address, String
  key :city,    String
  key :state,   String
  key :zip,     Integer
end

class KeyTest < Test::Unit::TestCase
  include MongoMapper

  context "The Key Class" do
    should "have the native types defined" do
      Key::NativeTypes.should == [String, Float, Time, Integer, Boolean, Array, Hash]
    end
  end

  context "Initializing a new key" do
    should "allow setting the name" do
      Key.new(:foo, String).name.should == 'foo'
    end

    should "allow setting the type" do
      Key.new(:foo, Integer).type.should be(Integer)
    end

    should "allow setting options" do
      Key.new(:foo, Integer, :required => true).options[:required].should be(true)
    end

    should "default options to {}" do
      Key.new(:foo, Integer, nil).options.should == {}
    end

    should "symbolize option keys" do
      Key.new(:foo, Integer, 'required' => true).options[:required].should be(true)
    end
    
    should "work with just name" do
      key = Key.new(:foo)
      key.name.should == 'foo'
    end
    
    should "work with name and type" do
      key = Key.new(:foo, String)
      key.name.should == 'foo'
      key.type.should == String
    end
    
    should "work with name, type, and options" do
      key = Key.new(:foo, String, :required => true)
      key.name.should == 'foo'
      key.type.should == String
      key.options[:required].should be_true
    end
    
    should "work with name and options" do
      key = Key.new(:foo, :required => true)
      key.name.should == 'foo'
      key.options[:required].should be_true
    end
  end

  context "A key" do
    should "be equal to another key with same name and type" do
      Key.new(:name, String).should == Key.new(:name, String)
    end

    should "not be equal to another key with different name" do
      Key.new(:name, String).should_not == Key.new(:foo, String)
    end

    should "not be equal to another key with different type" do
      Key.new(:name, String).should_not == Key.new(:name, Integer)
    end
    
    context "native?" do
      should "be true if native type" do
        Key.new(:name, String).native?.should be_true
      end
      
      should "be true if no type" do
        Key.new(:name).native?.should be_true
      end
      
      should "be false if not native" do
        Key.new(:name, Class.new).native?.should be_false
      end
    end

    should "know if it is a embedded_document" do
      klass = Class.new do
        include MongoMapper::EmbeddedDocument
      end
      Key.new(:name, klass).embedded_document?.should be_true
    end

    should "know if it is not a embedded_document" do
      Key.new(:name, String).embedded_document?.should be_false
    end
  end

  context "setting a value" do
    should "correctly typecast Strings" do
      key = Key.new(:foo, String)
      [21, '21'].each do |a|
        key.set(a).should == '21'
      end
    end

    should "correctly typecast Integers" do
      key = Key.new(:foo, Integer)
      [21, 21.0, '21'].each do |a|
        key.set(a).should == 21
      end
    end
    
    should "work fine with long integers" do
      key = Key.new(:foo, Integer)
      [9223372036854775807, '9223372036854775807'].each do |value|
        key.set(value).should == 9223372036854775807
      end
    end

    should "correctly typecast Floats" do
      key = Key.new(:foo, Float)
      [21, 21.0, '21'].each do |a|
        key.set(a).should == 21.0
      end
    end

    should "correctly typecast Times" do
      key = Key.new(:foo, Time)
      key.set('2000-01-01 01:01:01.123456').should == Time.local(2000, 1, 1, 1, 1, 1, 123456)
    end

    should "correctly typecast Times into UTC time zone" do
      key = Key.new(:foo, Time)
      key.set('2000-01-01 01:01:01.123456').zone.should == "UTC"
    end

    should "correctly typecast Boolean" do
      key = Key.new(:foo, Boolean)
      ['false', false, 'f', '0', 0].each do |b|
        key.set(b).should == false
      end

      ['true', true, 't', '1', 1].each do |b|
        key.set(b).should == true
      end
    end

    should "correctly typecast Array" do
      key = Key.new(:foo, Array)
      key.set([1,2,3,4]).should == [1,2,3,4]
      key.set({'1' => '2', '3' => '4'}).should == [['1', '2'], ['3', '4']]
      key.set('1').should == ['1']
    end

    should "correctly typecast Hash using indifferent access" do
      key = Key.new(:foo, Hash)
      key.set(:foo => 'bar')[:foo].should == 'bar'
      key.set(:foo => 'bar')['foo'].should == 'bar'
      key.set(:foo => {:bar => 'baz'})[:foo][:bar].should == 'baz'
      key.set(:foo => {:bar => 'baz'})['foo']['bar'].should == 'baz'
    end
  end

  context "getting a value" do
    should "work" do
      key = Key.new(:foo, String)
      key.get('bar').should == 'bar'
    end
    
    should "work without type" do
      key = Key.new(:foo)
      key.get([1,"2"]).should == [1, "2"]
      key.get(false).should   == false
      key.get({}).should      == {}
    end

    context "for a key with a default value set" do
      setup do
        @key = Key.new(:foo, String, :default => 'baz')
      end

      should "return default value if value nil" do
        @key.get(nil).should == 'baz'
      end

      should "return value if not blank" do
        @key.get('foobar').should == 'foobar'
      end
    end

    context "for a boolean key" do
      should "allow setting default to false" do
        Key.new(:active, Boolean, :default => false).get(nil).should be_false
      end

      should "allow setting default to true" do
        Key.new(:active, Boolean, :default => true).get(nil).should be_true
      end
    end

    context "for an array" do
      should "return array" do
        key = Key.new(:foo, Array)
        key.get([1,2]).should == [1,2]
      end

      should "default to empty array" do
        key = Key.new(:foo, Array)
        key.get(nil).should == []
      end
    end

    context "for a hash" do
      should "default to empty hash" do
        key = Key.new(:foo, Hash)
        key.get(nil).should == {}
      end

      should "use hash with indifferent access" do
        key = Key.new(:foo, Hash)
        key.get({:foo => 'bar'})['foo'].should == 'bar'
        key.get({:foo => 'bar'})[:foo].should == 'bar'
      end
    end

    context "for a embedded_document" do
      should "default to nil" do
        key = Key.new(:foo, Address)
        key.get(nil).should be_nil
      end

      should "return instance if instance" do
        address = Address.new(:city => 'South Bend', :state => 'IN', :zip => 46544)
        key = Key.new(:foo, Address)
        key.get(address).should == address
      end
    end
  end
end # KeyTest