
require "#{File.dirname(__FILE__)}/../test_helper"

class SearchTest < Test::Unit::TestCase

  S = Ultrasphinx::Search
  E = Ultrasphinx::UsageError
  STRFTIME = "%b %d %Y %H:%M:%S" # Chronic can't parse the default date .to_s

  def test_simple_query
    assert_nothing_raised do
      @s = S.new(:query => 'seller').run
    end
    assert_equal 20, @s.size
  end  
  
  def test_with_subtotals_option
    S.client_options['with_subtotals'] = true
    
    # No delta index; accurate subtotals sum
    @s = S.new(:indexes => Ultrasphinx::MAIN_INDEX).run
    assert_equal @s.total_entries, @s.subtotals.values._sum
    
    # With delta; subtotals sum not less than total sum
    @s = S.new.run
    assert @s.subtotals.values._sum >= @s.total_entries 
    
    # With delta and filter; request class gets accurate count regardless
    @s = S.new(:class_names => 'Seller').run
    assert_equal @s.total_entries, @s.subtotals['Seller']    
    assert @s.subtotals.values._sum >= @s.total_entries 
    
    S.client_options['with_subtotals'] = false
  end
  
  def test_ignore_missing_records_option
    S.client_options['distance'] = false # must disable geodistance or line 57 will bomb
    @s = S.new(:per_page => 1).run
    @record = @s.first
    assert_equal 1, @s.size
    
    @record.destroy

    assert_raises(ActiveRecord::RecordNotFound) do
      @s = S.new(:per_page => 1).run
    end    
    
    S.client_options['ignore_missing_records'] = true
    assert_nothing_raised do
      @s = S.new(:per_page => 1).run
    end 
    assert_equal 0, @s.size
    assert_equal 1, @s.per_page
    S.client_options['ignore_missing_records'] = false
  
    # Re-insert the record... ugh
    @new_record = @record.class.new(@record.attributes)
    @new_record.id = @record.id
    @new_record.save!
  end
  
  def test_query_retries_and_fails
    system("cd #{RAILS_ROOT}; rake ultrasphinx:daemon:stop &> /dev/null")
    assert_raises(Ultrasphinx::DaemonError) do
      S.new.run
    end
    system("cd #{RAILS_ROOT}; rake ultrasphinx:daemon:start &> /dev/null")
  end
  
  def test_accessors
    @per_page = 5
    @page = 3
    @s = S.new(:query => 'seller', :per_page => @per_page, :page => @page).run
    assert_equal @per_page, @s.per_page
    assert_equal @page, @s.page
    assert_equal @page - 1, @s.previous_page
    assert_equal @page + 1, @s.next_page
    assert_equal @per_page * (@page - 1), @s.offset
    assert @s.page_count >= @s.total_entries / @per_page.to_f
   end
  
  def test_empty_query
    assert_nothing_raised do
      @s = S.new.run
    end 
  end
  
  def test_total_entries
    @total = Ultrasphinx::MODEL_CONFIGURATION.keys.inject(0) do |acc, class_name| 
      acc + class_name.constantize.count
    end - User.count(:conditions => {:deleted => true })
    
    assert_equal(
      @total,
      @s = S.new.run.total_entries
    )  
  end
  
  def test_individual_totals_with_pagination
    Ultrasphinx::MODEL_CONFIGURATION.keys.each do |class_name| 
      if class_name == "User"
        assert_equal User.count(:conditions => {:deleted => false }), 
          S.new(:class_names => class_name, :page => 2).total_entries
      else
        assert_equal class_name.constantize.count, 
          S.new(:class_names => class_name, :page => 2).total_entries
      end
    end
  end

  def test_individual_totals_without_pagination
    Ultrasphinx::MODEL_CONFIGURATION.keys.each do |class_name| 
      begin
        if class_name == "User"
          assert_equal User.count(:conditions => {:deleted => false }), 
            S.new(:class_names => class_name).total_entries
        else
          assert_equal class_name.constantize.count, 
            S.new(:class_names => class_name).total_entries
        end
      rescue Object
        raise class_name
      end
    end
  end
  
  def test_sort_by_date
    assert_equal(
      Seller.find(:all, :limit => 5, :order => 'created_at DESC').map(&:created_at),
      S.new(:class_names => 'Seller', :sort_by => 'created_at', :sort_mode => 'descending', :per_page => 5).run.map(&:created_at)
    )
  end

  def test_sort_by_float
    assert_equal(
      Seller.find(:all, :limit => 5, :order => 'capitalization ASC').map(&:capitalization),
      S.new(:class_names => 'Seller', :sort_by => 'capitalization', :sort_mode => 'ascending', :per_page => 5).run.map(&:capitalization)
    )
  end
  
  def test_sort_by_string
    assert_equal(
      Seller.find(:all, :limit => 5, :order => 'mission_statement ASC').map(&:mission_statement),
      S.new(:class_names => 'Seller', :sort_by => 'mission_statement', :sort_mode => 'ascending', :per_page => 5).run.map(&:mission_statement)
    )
    assert S.new(:sort_by => 'mission_statement', :sort_mode => 'ascending').run.size > 0
  end
 
  def test_sort_by_string_using_query
    assert_equal(
      Seller.find(10,11, :order => 'mission_statement ASC').map(&:mission_statement),
      S.new(:class_names => 'Seller', :query => 'seller10 or seller11', :sort_by => 'mission_statement', :sort_mode => 'ascending', :per_page => 2).run.map(&:mission_statement)
    )
  end 
 
  def test_filter
    assert_equal(
      Seller.count(:conditions => 'user_id = 17'),
      S.new(:class_names => 'Seller', :filters => {'user_id' => 17}).run.size
    )
  end
  
  def test_nil_filter
    # XXX
  end
  
  def test_float_range_filter
    @count = Seller.count(:conditions => 'capitalization <= 29.5 AND capitalization >= 10')
    assert_equal(@count,
      S.new(:class_names => 'Seller', :filters => {'capitalization' => 10..29.5}).run.size)
    assert_equal(@count,
      S.new(:class_names => 'Seller', :filters => {'capitalization' => 29.5..10}).run.size)
  end

  def test_date_range_filter
    @first, @last = Seller.find(5).created_at, Seller.find(10).created_at
    @items = Seller.find(:all, :conditions => ['created_at >= ? AND created_at <= ?', @last, @first]).sort_by(&:id)
    @count = @items.size
    
    @search = S.new(:class_names => 'Seller', :filters => {'created_at' => @first..@last}).run.sort_by(&:id)
    assert_equal(@count, @search.size)
    assert_equal(@items.first.created_at, @search.first.created_at)
    assert_equal(@items.last.created_at, @search.last.created_at)
    
    assert_equal(@count,
      S.new(:class_names => 'Seller', :filters => {'created_at' => @last..@first}).run.size)
    assert_equal(@count,
      S.new(:class_names => 'Seller', :filters => {'created_at' => @last.strftime(STRFTIME)..@first.strftime(STRFTIME)}).run.size)

    assert_raises(Ultrasphinx::UsageError) do
      S.new(:class_names => 'Seller', :filters => {'created_at' => "bogus".."sugob"}).run.size
    end
  end
    
  def test_text_filter
    assert_equal(
      Seller.count(:conditions => "company_name = 'seller17'"),
      S.new(:class_names => 'Seller', :filters => {'company_name' => 'seller17'}).run.size
    )  
  end

  def test_exclusion_filter
    assert_equal(
      Seller.count(:conditions => 'user_id = 17'),
      S.new(:class_names => 'Seller', :filters => { 'user_id' => { 'value' => 17, 'exclude' => false } }).run.size
    )
    assert_equal(
      Seller.count(:conditions => 'user_id != 17'),
      S.new(:class_names => 'Seller', :filters => { 'user_id' => { 'value' => 17, 'exclude' => true } }).run.size
    )
  end
  
  def test_invalid_filter
    assert_raises(Ultrasphinx::UsageError) do
      S.new(:class_names => 'Seller', :filters => {'bogus' => 17}).run
    end
  end
  
  def test_conditions
    @deleted_count = User.count(:conditions => {:deleted => true })
    assert_equal 1, @deleted_count
    assert_equal User.count - @deleted_count, S.new(:class_names => 'User').run.total_entries 
  end
  
  #  def test_mismatched_facet_configuration
  #    # XXX Should be caught at configuration time. For now it's your own fault 
  #    # if you do it and get confused.
  #    assert_raises(Ultrasphinx::ConfigurationError) do 
  #      Ultrasphinx::Search.new(:facets => 'company_name').run
  #    end
  #  end
  
  def test_bogus_facet_name
    assert_raises(Ultrasphinx::UsageError) do
      Ultrasphinx::Search.new(:facets => 'bogus').run
    end
  end  
  
  def test_unconfigured_sortable_name
    assert_raises(Ultrasphinx::UsageError) do
      S.new(:class_names => 'User', :sort_by => 'company', :sort_mode => 'ascending', :per_page => 5).run
    end
  end
  
  def test_sorting_by_field_with_relevance_order
    assert_raises(Ultrasphinx::UsageError) do
      # Defaults to :sort_mode => 'relevance'
      S.new(:class_names => 'Seller', :sort_by => 'created_at', :per_page => 5).run 
    end  
  end
  
  def test_nonexistent_sortable_name
    assert_raises(Ultrasphinx::UsageError) do
      S.new(:class_names => 'Seller', :sort_by => 'bogus', :per_page => 5).run
    end  
  end
  
  def test_text_facet
    @s = Ultrasphinx::Search.new(:facets => ['company_name']).run
    assert_equal(
      (Seller.count + 1), 
      @s.facets['company_name'].size
    )
  end

  def test_included_text_facet_without_association_sql
    # There are 40 users, but only 20 sellers. So you get 20 facets + 1 nil with 20 items
    @s = Ultrasphinx::Search.new(:class_names => 'User', :facets => ['company']).run
    assert_equal(
      (Seller.count + 1), 
      @s.facets['company'].size
    )
  end

  def test_included_text_facet_with_association_sql
    # XXX there are 40 users but only 20 sellers, but the replace function from User deletes 
    # User #6 and 16 (why? Hash collision?). There is also a nil facet that gets added for a 
    # total of 19 objects
    @s = Ultrasphinx::Search.new(:class_names => 'User', :facets => ['company_two']).run
    assert_equal(
      (Seller.count - 1), 
      @s.facets['company_two'].size
    )
  end
  
  def test_numeric_facet
    @user_id_count = Seller.find(:all).map(&:user_id).uniq.size

    @s = Ultrasphinx::Search.new(:class_names => 'Seller', :facets => 'user_id').run
    assert_equal @user_id_count, @s.facets['user_id'].size

    @s = Ultrasphinx::Search.new(:facets => 'user_id').run
    assert_equal @user_id_count + 1, @s.facets['user_id'].size
    assert @s.facets['user_id'][0] > 1
  end
  
  def test_multi_facet
    @facets = ['user_id', 'capitalization', 'company_name']
    @s = Ultrasphinx::Search.new(:facets => @facets).run
    @facets.each do |facet|
      assert @s.facets[facet]
      assert @s.facets[facet].any?
    end
  end
  
  def test_float_facet
    @s = Ultrasphinx::Search.new(:class_names => 'Seller', :facets => 'capitalization').run
    @s.facets['capitalization'].keys.each do |key|
      # XXX http://www.sphinxsearch.com/forum/view.html?id=963
      # assert key.is_a?(Float)
    end
  end
  
  def test_table_aliasing_and_association_sql
    assert_equal 2, Ultrasphinx::Search.new(:class_names => 'User', :query => 'company_two:replacement').run.size
  end
    
  def test_weights
    @unweighted = Ultrasphinx::Search.new(:query => 'seller16', :per_page => 1).run.first
    @weighted = Ultrasphinx::Search.new(:query => 'seller16', :weights => {'company' => 2}, :per_page => 1).run.first
    assert_not_equal @unweighted, @weighted
  end
  
  def test_excerpts
    @s = Ultrasphinx::Search.new(:class_names => 'Seller', :query => 'seller10')
    @excerpted_item = @s.excerpt.first
    @item = @s.run.first
    assert_not_equal @item.name, @excerpted_item.name
    assert_match /strong/, @excerpted_item.name
  end
  
  def test_distance_ascending
    # (21.289453, -157.842783) is Ala Moana Shopping Center, 1450 Ala Moana Blvd, Honolulu HI 96814
    @s = Ultrasphinx::Search.new(:class_names => 'Geo::Address', 
          :query => 'Honolulu',
          :per_page => 40,
          :sort_mode => 'extended',
          :sort_by => 'distance',
          :location => {
            :lat => 21.289453,
            :long => -157.842783
          })
    @s.run
    # This should return all items in the database, sorted in increasing distance
    assert_equal 40, @s.size
    assert_match /Waikiki Aquarium/, @s.first.name
    assert_match /Waikiki Aquarium/, @s[1].name
    assert_in_delta 3439, @s.first.distance, 10
  end
  
  def test_distance_filter
    # (21.289453, -157.842783) is Ala Moana Shopping Center, 1450 Ala Moana Blvd, Honolulu HI 96814
    @s = Ultrasphinx::Search.new(:class_names => 'Geo::Address', 
          :query => 'Honolulu',
          :per_page => 40,
          :sort_mode => 'extended',
          :sort_by => 'distance',
          :filters => {'distance' => 0..5000},
          :location => {
            :lat => 21.289453,
            :long => -157.842783
          })
    @s.run

    @s.each do |obj|
      # This should return only those items within 5000 meters of Ala Moana, which 
      # is only Waikiki Aquarium and Diamond Head.
      assert_match /Waikiki Aquarium|Diamond Head/, obj.name
    end
        
    assert_in_delta 3439, @s.first.distance, 10 # Closest item should be Waikiki at 3439 meters
  end
  
  def test_distance_decending
    # (21.289453, -157.842783) is Ala Moana Shopping Center, 1450 Ala Moana Blvd, Honolulu HI 96814
    @s = Ultrasphinx::Search.new(:class_names => 'Geo::Address', 
          :query => 'Honolulu',
          :per_page => 40,
          :sort_mode => 'extended',
          :sort_by => 'distance desc',
          :location => {
            :lat => 21.289453,
            :long => -157.842783
          })
    @s.run
    
    # Ids should come back in reverse order because the fixtures have the farther locations later in the file
    assert_equal 40, @s.size
    assert_match /Kailua Beach Park/, @s.first.name 
    assert_in_delta 16940, @s.first.distance, 40
  end
end
