require File.dirname(__FILE__) + '/test_helper.rb'

class TestSdb < Test::Unit::TestCase
  DOMAIN_PREFIX = 'right_sdb_awesome_test'
  CLIENT_DOMAIN = "#{DOMAIN_PREFIX}_client"
  PERSON_DOMAIN = "#{DOMAIN_PREFIX}_person"

  class Client < RightAws::ActiveSdb::Base
    set_domain_name CLIENT_DOMAIN
  end

  class Person < RightAws::ActiveSdb::Base
    set_domain_name PERSON_DOMAIN

    columns do
      name
      email
      score         :Integer
      is_active     :Boolean
      registered_at :DateTime
      created_at    :DateTime, :default => lambda{ Time.now }
    end
  end

  def setup
    STDOUT.sync  = true
    @clients = [ 
      { 'name' => 'Bush',     'country' => 'USA',    'gender' => 'male',   'expiration' => '2009', 'post' => 'president' },
      { 'name' => 'Putin',    'country' => 'Russia', 'gender' => 'male',   'expiration' => '2008', 'post' => 'president' },
      { 'name' => 'Medvedev', 'country' => 'Russia', 'gender' => 'male',   'expiration' => '2012', 'post' => 'president' },
      { 'name' => 'Mary',     'country' => 'USA',    'gender' => 'female', 'hobby' => ['patchwork', 'bundle jumping'] },
      { 'name' => 'Sandy',    'country' => 'Russia', 'gender' => 'female', 'hobby' => ['flowers', 'cats', 'cooking'] },
      { 'name' => 'Mary',     'country' => 'Russia', 'gender' => 'female', 'hobby' => ['flowers', 'cats', 'cooking'] } ]
    @people = [
      { :name => 'Yetta E. Andrews',    :email => 'nulla.facilisis@metus.com', :score => 100, :is_active => true,  :registered_at => Time.local(2000, 1, 1) },
      { :name => 'Sybill O. Olson',     :email => 'nisi.Aenean.eget@urna.com', :score =>  87, :is_active => true,  :registered_at => Time.local(2008, 7, 6) },
      { :name => 'Isabelle K. Flynn',   :email => 'velit@amet.com',            :score =>  98, :is_active => false, :registered_at => Time.local(2003, 5, 20) },
      { :name => 'Juliet H. Witt',      :email => 'egestas@pretiumaliquet.ca', :score =>  72, :is_active => true,  :registered_at => Time.local(2007, 2, 28) },
      { :name => 'Lucy N. Christensen', :email => 'lacus.v12@stu.edu',         :score =>  94, :is_active => false, :registered_at => Time.local(2005, 10, 26) }
    ]
    RightAws::ActiveSdb.establish_connection(TestCredentials.aws_access_key_id, TestCredentials.aws_secret_access_key)
  end

  SDB_DELAY = 3
  
  def wait(delay, msg='')
    print "     waiting #{delay} seconds: #{msg}"
    while delay>0 do
      delay -= 1
      print '.'
      sleep 1
    end
    puts
  end

  #---------------------------
  # Rightscale::SdbInterface
  #---------------------------

  def test_00_delete_domain
    assert RightAws::ActiveSdb.delete_domain(CLIENT_DOMAIN)
    assert RightAws::ActiveSdb.delete_domain(PERSON_DOMAIN)
    wait SDB_DELAY, 'test 00: after domain deletion'
  end
  
  def test_01_create_domain
    # check that domain does not exist
    assert !RightAws::ActiveSdb.domains.include?(CLIENT_DOMAIN)
    # create domain
    assert Client.create_domain
    assert Person.create_domain
    wait SDB_DELAY, 'test 01: after domain creation'
    # check that we have received new domain from Amazin
    assert RightAws::ActiveSdb.domains.include?(CLIENT_DOMAIN)
  end

  def test_02_create_items
    # check that DB is empty
    clients = Client.find(:all)
    assert clients.right_blank?
    # put some clients there
    @clients.each do |client|
      Client.create client
    end
    wait SDB_DELAY, 'test 02: after clients creation'
    # check that DB has all the clients we just putted
    clients = Client.find(:all)
    assert_equal @clients.size, clients.size
  end
  
  def test_03_create_and_save_new_item
    # get the db
    old_clients = Client.find(:all)
    # create new client
    new_client = Client.new('country' => 'unknown', 'dummy' => 'yes')
    wait SDB_DELAY, 'test 03: after in-memory client creation'
    # get the db and ensure we created the client in-memory only
    assert_equal old_clients.size, Client.find(:all).size
    # put the client to DB
    new_client.save
    wait SDB_DELAY, 'test 03: after in-memory client saving'
    # get all db again and compare to original list
    assert_equal old_clients.size+1, Client.find(:all).size
  end

  def test_04_find_all
    # retrieve all the DB, make sure all are in place
    clients = Client.find(:all)
    ids = clients.map{|client| client.id }[0..1]
    assert_equal @clients.size + 1, clients.size
    # retrieve all presidents (must find: Bush, Putin, Medvedev)
    assert_equal 3, Client.find(:all, :conditions => ["[?=?]",'post','president']).size
    # retrieve all russian presidents (must find: Putin, Medvedev)
    assert_equal 2, Client.find(:all, :conditions => ["['post'=?] intersection ['country'=?]",'president', 'Russia']).size
    # retrieve all russian presidents and all women (must find: Putin, Medvedev, 2 Maries and Sandy)
    assert_equal 5, Client.find(:all, :conditions => ["['post'=?] intersection ['country'=?] union ['gender'=?]",'president', 'Russia','female']).size
    # find all rissian presidents Bushes
    assert_equal 0, Client.find(:all, :conditions => ["['post'=?] intersection ['country'=?] intersection ['name'=?]",'president', 'Russia','Bush']).size
    # --- find by ids
    # must find 1 rec (by rec id) and return it
    assert_equal ids.first, Client.find(ids.first).id
    # must find 1 rec (by one item array) and return an array
    assert_equal ids.first, Client.find([ids.first]).first.id
    # must find 2 recs (by a list of comma separated ids) and return an array
    assert_equal ids.size, Client.find(*ids).size
    # must find 2 recs (by an array of ids) and return an array
    assert_equal ids.size, Client.find(ids).size
    ids << 'dummy_id'
    # must raise an error when getting unexistent record
    assert_raise(RightAws::ActiveSdb::ActiveSdbError) do 
      Client.find(ids)
    end
    # find one record by unknown id
    assert_raise(RightAws::ActiveSdb::ActiveSdbError) do
      Client.find('dummy_id')
    end
  end

  def test_05_find_first
    # find any record
    assert Client.find(:first)
    # find any president
    assert Client.find(:first, :conditions => ["[?=?]",'post','president'])
    # find any rissian president
    assert Client.find(:first, :conditions => ["['post'=?] intersection ['country'=?]",'president','Russia'])
    # find any unexistent record
    assert_nil Client.find(:first, :conditions => ["['post'=?] intersection ['country'=?]",'president','Rwanda'])
  end

  def test_06_find_all_by_helpers
    # find all Bushes
    assert_equal 1, Client.find_all_by_name('Bush').size
    # find all russian presidents
    assert_equal 2, Client.find_all_by_post_and_country('president','Russia').size
    # find all women in USA that love flowers
    assert_equal 2, Client.find_all_by_gender_and_country_and_hobby('female','Russia','flowers').size
    # order and auto_load:
    clients = Client.find_all_by_post('president', :order => 'name', :auto_load => true)
    assert_equal [['Bush'], ['Medvedev'], ['Putin']], clients.map{|c| c['name']}
    clients = Client.find_all_by_post('president', :order => 'name desc', :auto_load => true)
    assert_equal [['Putin'], ['Medvedev'], ['Bush']], clients.map{|c| c['name']}
  end
  
  def test_07_find_by_helpers
    # find mr Bush
    assert Client.find_by_name('Bush')
    # find any russian president
    assert Client.find_by_post_and_country('president','Russia')
    # find Mary in Russia that loves flowers
    # order and auto_load:
    assert_equal ['Bush'],  Client.find_by_post('president', :order => 'name',      :auto_load => true)['name']
    assert_equal ['Putin'], Client.find_by_post('president', :order => 'name desc', :auto_load => true)['name']
  end

  def test_08_reload
    putin = Client.find_by_name('Putin')
    # attributes must be empty until reload (except 'id' field)
    assert_nil putin['name']
    assert_nil putin['country']
    assert_nil putin['gender']
    assert_nil putin['expiration']
    assert_nil putin['post']
    # reloaded attributes must have 5 items + id
    putin.reload
    assert_equal 6, putin.attributes.size
    # check all attributes
    assert_equal ['Putin'],     putin['name']
    assert_equal ['Russia'],    putin['country']
    assert_equal ['male'],      putin['gender']
    assert_equal ['2008'],      putin['expiration']
    assert_equal ['president'], putin['post']
  end

  def test_09_select
    # select all records
    assert_equal 7, Client.select(:all).size
    # LIMIT
    # 1 record
    assert Client.select(:first).is_a?(Client)
    # select 2 recs
    assert_equal 2, Client.select(:all, :limit => 2).size
    # ORDER
    # select all recs ordered by 'expration' (must find only recs where 'expration' attribute presents)
    result = Client.select(:all, :order => 'expiration')
    assert_equal 3, result.size
    assert_equal ['2008', '2009', '2012'], result.map{ |c| c['expiration'] }.flatten
    # desc order
    result = Client.select(:all, :order => 'expiration desc')
    assert_equal ['2012', '2009', '2008'], result.map{ |c| c['expiration'] }.flatten
    # CONDITIONS
    result = Client.select(:all, :conditions => ["expiration >= ?", 2009], :order => 'name')
    assert_equal ['Bush', 'Medvedev'], result.map{ |c| c['name'] }.flatten
    result = Client.select(:all, :conditions => "hobby='flowers' AND gender='female'", :order => 'name')
    assert_equal ['Mary', 'Sandy'], result.map{ |c| c['name'] }.flatten
    # SELECT
    result = Client.select(:all, :select => 'hobby', :conditions => "gender IS NOT NULL", :order => 'name')
    hobbies = result.map{|c| c['hobby']}
    # must return all recs
    assert_equal 6, result.size
    # but anly 3 of them have this field set
    assert_equal 3, hobbies.compact.size
  end

  def test_10_select_by
    assert_equal 2, Client.select_all_by_hobby('flowers').size
    assert_equal 2, Client.select_all_by_hobby_and_country('flowers', 'Russia').size
    assert_equal ['Putin'], Client.select_by_post_and_expiration('president','2008')['name']
  end
  
  def test_11_save_and_put
    putin = Client.find_by_name('Putin')
    putin.reload
    putin['hobby'] = 'ski'
    # SAVE method (replace values)
    putin.save
    wait SDB_DELAY, 'test 09: after saving'
    # check that DB was updated with 'ski'
    new_putin = Client.find_by_name('Putin')
    new_putin.reload
    assert ['ski'], new_putin['hobby']
    # replace hobby
    putin['hobby'] = 'dogs'
    putin.save
    wait SDB_DELAY, 'test 09: after saving'
    # check that 'ski' in DB was replaced by 'dogs'
    new_putin = Client.find_by_name('Putin')
    new_putin.reload
    assert ['dogs'], new_putin['hobby']
    # PUT method (add values)
    putin['hobby'] = 'ski'
    putin.put
    wait SDB_DELAY, 'test 09: after putting'
    # check that 'ski' was added to 'dogs'
    new_putin = Client.find_by_name('Putin')
    new_putin.reload
    assert ['dogs', 'ski'], new_putin['hobby'].sort
  end

  def test_12_save_and_put_attributes
    putin = Client.find_by_name('Putin')
    putin.reload
    # SAVE method (replace values)
    putin.save_attributes('language' => 'russian')
    wait SDB_DELAY, 'test 10: after save_attributes'
    # check that DB was updated with 'ski'
    new_putin = Client.find_by_name('Putin')
    new_putin.reload
    assert ['russian'], new_putin['language']
    # replace 'russian' by 'german'
    putin.save_attributes('language' => 'german')
    wait SDB_DELAY, 'test 10: after save_attributes'
    # check that 'russian' in DB was replaced by 'german'
    new_putin = Client.find_by_name('Putin')
    new_putin.reload
    assert ['german'], new_putin['language']
    # PUT method (add values)
    putin.put_attributes('language' => ['russian', 'english'])
    wait SDB_DELAY, 'test 10: after put_attributes'
    # now Putin must know all the languages
    new_putin = Client.find_by_name('Putin')
    new_putin.reload
    assert ['english', 'german', 'russian'], new_putin['language'].sort
  end
  
  def test_13_delete
    putin = Client.find_by_name('Putin')
    putin.reload
    # --- delete_values
    # remove an unknown attribute
    # should return an empty hash
    assert_equal( {}, putin.delete_values('undefined_attribute' => 'ohoho'))
    # remove 2 languages
    lang_hash = {'language' => ['english', 'german']}
    assert_equal lang_hash, putin.delete_values(lang_hash)
    wait SDB_DELAY, 'test 11: after put_attributes'
    # now Putin must know only russian lang
    new_putin = Client.find_by_name('Putin')
    new_putin.reload
    assert ['russian'], new_putin['language'].sort
    # --- delete_attributes
    putin.delete_attributes('language', 'hobby')
    wait SDB_DELAY, 'test 11: after delete_attributes'
    # trash hoddy and langs
    new_putin = Client.find_by_name('Putin')
    new_putin.reload
    assert_nil new_putin['language']
    assert_nil new_putin['hobby']
    # --- delete item
    putin.delete
    wait SDB_DELAY, 'test 11: after delete item'
    assert_nil Client.find_by_name('Putin')
  end

  def test_14_dynamic_attribute_accessors
    bush = Client.find_by_name('Bush')
    bush.reload
    assert_nothing_raised {
      assert_equal ['male'], bush.gender
      bush.name = 'George'
      assert_equal ['George'], bush.name
    }
    assert_raise(NoMethodError) { bush.flarble }
  end

  def test_15_column_emulation
    @people.each do |person|
      Person.create person
    end
    wait SDB_DELAY, 'test 15: after people creation'
    person = Person.find_by_email 'nulla.facilisis@metus.com'
    person.reload

    assert_equal 'Yetta E. Andrews', person.name
    assert_equal DateTime, person.registered_at.class
    assert person['registered_at'].is_a?(DateTime)
    assert person[:registered_at].is_a?(DateTime)

    assert ! person[:created_at].nil?
    assert_equal DateTime, person.created_at.class
    assert person['created_at'].is_a?(DateTime)
    assert person[:created_at].is_a?(DateTime)

    assert person.is_active

    assert_equal 100, person.score
  end

  def test_999_delete_domain
    assert Client.delete_domain
    assert Person.delete_domain
    wait SDB_DELAY, 'test 999: after delete domain'
    assert_raise(Rightscale::AwsError) do
      Client.find :all
    end
  end
    
end