require File.expand_path("#{File.dirname(__FILE__)}/../test_helper")
module Solr; end

class InstanceMethodsTest < Test::Unit::TestCase

  context "With a Solr record instance" do
    setup do
      @instance = SolrInstance.new
    end
    
    context "when checking whether indexing is disabled" do
    
      setup do
        @instance.configuration = {:if => true}
      end

      should "return true if the specified proc returns true " do
        @instance.configuration[:offline] = proc {|record| true}
        assert @instance.indexing_disabled?
      end
  
      should "return false if the specified proc returns false" do
        @instance.configuration[:offline] = proc {|record| false}
        assert !@instance.indexing_disabled?
      end
  
      should "return true if no valid offline option was specified" do
        @instance.configuration[:offline] = nil
        @instance.configuration[:if] = proc {true}
        assert !@instance.indexing_disabled?
      end
    end

    context "when validating the boost" do
      setup do
        @instance.solr_configuration = {:default_boost => 10.0}
        @instance.configuration = {:if => true}
      end
    
      should "accept and evaluate a block" do
        @instance.configuration[:boost] = proc {|record| record.boost_rate}
        assert_equal 10.0, @instance.send(:validate_boost, @instance.configuration[:boost])
      end
  
      should "accept and return a float" do
        @instance.configuration[:boost] = 9.0
        assert_equal 9.0, @instance.send(:validate_boost, @instance.configuration[:boost])
      end
  
      should "return the default float when the specified is negative" do
        @instance.configuration[:boost] = -1.0
        assert_equal 10.0, @instance.send(:validate_boost, @instance.configuration[:boost])
      end
  
      should "execute the according method when value is a symbol" do
        @instance.configuration[:boost] = :irate
        assert_equal 8.0, @instance.send(:validate_boost, @instance.configuration[:boost])
      end
  
      should "return the default boost when there is no valid boost" do
        @instance.configuration[:boost] = "boost!"
        assert_equal 10.0, @instance.send(:validate_boost, @instance.configuration[:boost])
      end
    end
  
    context "when determining the solr document id" do
      should "combine class name and id" do
        assert_equal "SolrInstance:10", @instance.solr_id
      end
    end
  
    context "when saving the instance to solr" do
      context "with indexing disabled" do
        setup do
          @instance.configuration = {:fields => [:name], :if => nil}
        end
  
        should "just return and do nothing" do
          @instance.expects(:solr_add).never
          @instance.expects(:solr_destroy).never
          assert @instance.solr_save
        end
      end
    
      context "with indexing enabled" do
        setup do
          @instance.configuration = {:fields => [:name], :if => "true", :auto_commit => true}
          @instance.stubs(:solr_commit)
          @instance.stubs(:solr_add)
          @instance.stubs(:to_solr_doc).returns("My test document")
        end

        should "add the solr document" do
          @instance.expects(:solr_add).with("My test document").once
          @instance.solr_save
        end
      
        should "commit to solr" do
          @instance.expects(:solr_commit).once
          @instance.solr_save
        end
      
        should "not commit if auto_commit is disabled" do
          @instance.configuration.merge!(:auto_commit => false)
          @instance.expects(:solr_commit).never
          @instance.solr_save
        end
      
        should "destroy the document if :if clause is false" do
          @instance.configuration.merge!(:if => "false")
          @instance.expects(:solr_destroy).once
          @instance.solr_save
        end
      end
    end
  
    context "when destroying an instance in solr" do
      setup do
        @instance.configuration = {:if => true, :auto_commit => true}
        @instance.stubs(:solr_commit)
        @instance.stubs(:solr_delete)
      end

      should "delete the instance" do
        @instance.expects(:solr_delete).with("SolrInstance:10")
        @instance.solr_destroy
      end
    
      should "commit to solr" do
        @instance.expects(:solr_commit)
        @instance.solr_destroy
      end
    
      should "not commit if auto_commit is disabled" do
        @instance.configuration.merge!(:auto_commit => false)
        @instance.expects(:solr_commit).never
        @instance.solr_destroy
      end
      
      context "with indexing disabled" do
        should "not contact solr" do
          @instance.configuration.merge!(:offline => true, :if => nil)
          @instance.expects(:solr_delete).never
          @instance.solr_destroy
        end
      end
    end
  
    context "when converting an instance to a solr document" do
      setup do
        @instance.configuration = {:if => true, :auto_commit => true, :fields => [:name, :last_name],
          :solr_fields => {:name => {:boost => 9.0}, :last_name => {:type => :string}}, :boost => 10.0}
        @instance.solr_configuration = {:type_field => "type", :primary_key_field => "pk_id", :default_boost => 25.0}
      end
    
      should "add a document boost" do
        assert_equal 10, @instance.to_solr_doc.boost
      end
      
      should "set the solr id" do
        assert_equal "SolrInstance:10", @instance.to_solr_doc[:id]
      end
      
      should "set the type field" do
        assert_equal "SolrInstance", @instance.to_solr_doc[:type]
      end
      
      should "set the primary key fields" do
        assert_equal("10", @instance.to_solr_doc[:pk_id])
      end
      
      should "add the includes if they were configured" do
        @instance.configuration.merge! :include => [:author]
        @instance.expects(:add_includes)
        @instance.to_solr_doc
      end
      
      context "with a local" do
        setup do
          @local = Local.new '14.51', '-65.43'
          @instance.stubs(:local).returns(@local)
          @instance.configuration[:spatial] = true
          @fields = @instance.to_solr_doc.fields
        end
      
        should "add the longitude" do
          field = @fields.find { |field| field.name.eql? "lng_f" }
          assert_equal @local.longitude, field.value
        end
        
        should "add the latitude" do
          field = @fields.find { |field| field.name.eql? "lat_f" }
          assert_equal @local.latitude, field.value
        end
      end
      
      context "without a local" do
        should "should ignore the spatial option" do
          @instance.stubs(:local).returns(nil)
          @instance.configuration[:spatial] = true
          fields = @instance.to_solr_doc.fields
          assert_equal nil, fields.find { |field| field.name.eql? 'lng_f' }
        end
      end
      
      context "with indexed fields" do
        should "add fields with type" do
          assert_equal "Chunky bacon!", @instance.to_solr_doc[:name_s]
        end
        
        should "add the field boost" do
          field = @instance.to_solr_doc.fields.find {|f| f.name.to_s == "name_s"}
          assert_equal 9.0, field.boost
        end
        
        should "set the default boost for the field, if none is configured" do
          @instance.configuration[:solr_fields][:name][:boost] = nil
          field = @instance.to_solr_doc.fields.find {|f| f.name.to_s == "name_s"}
          assert_equal 25.0, field.boost
        end

        should "ignore null fields" do
          @instance.stubs(:name_for_solr).returns(nil)
          @instance.stubs(:last_name_for_solr).returns('Dohn')
          assert ! @instance.to_solr_doc.fields.find {|f| f.name.to_s == "name_s"}
          assert @instance.to_solr_doc.fields.find {|f| f.name.to_s == "last_name_s"}
        end
        
        should "not overwrite the type or id field" do
          @instance.configuration[:solr_fields] = {:type => {}, :id => {}}
          doc = @instance.to_solr_doc
          assert_not_equal "humbug", doc[:type]
          assert_not_equal "bogus", doc[:id]
        end
        
        should "escape the contents" do
          @instance.name = "<script>malicious()</script>"
          assert_equal "&lt;script&gt;malicious()&lt;/script&gt;", @instance.to_solr_doc[:name_s]
        end

        should "use an alternate field name if specified in options" do
          @instance.stubs(:nickname_for_solr).returns('Nick')
          @instance.configuration[:solr_fields].merge! :nickname => {:as => :alias}
          doc = @instance.to_solr_doc
          assert_not_nil @instance.to_solr_doc.fields.find {|f| f.name.to_s == "alias_s"}
          assert_nil     @instance.to_solr_doc.fields.find {|f| f.name.to_s == "nickname_s"}
        end
        
        context "when indexing tags" do
          setup do
            taggings = [Tagging.new('house'), Tagging.new('beach')].sort { |a, b| rand - 0.5 }
            @instance.stubs(:taggings).returns(taggings)
            @instance.configuration[:taggable] = true
            @fields = @instance.to_solr_doc.fields
          end
          
          should "set the field name as tag of type text, so they can be found by a query without filters" do
            assert_equal "tag_t", @fields.last.name
          end
          
          should "set the field value with the tag value" do
            assert_equal @instance.taggings.last.tag.name, @fields.last.value
          end
          
          should "consider all of them" do
            assert_equal @instance.taggings.size * 2, @fields.select { |f| f.name.include? "tag_" }.size
          end
          
          should "facet them" do
            assert_equal @fields.select { |f| f.name.include? "tag_t" }.size, 
                         @fields.select { |f| f.name.include? "tag_facet" }.size
          end
        end

        context "when associations are included" do
          setup do
            class AssocLabel < String
              @@singular = {'people' => 'person'}
              def to_s
                self
              end
              def singularize
                @@singular[self]
              end
            end
            @assoc = AssocLabel.new('people')
            person = {:name => 'Hank Venture', :address => 'Venture Compound'}
            @people = [OpenStruct.new(person.merge(:attributes => person))]
            @instance.stubs(:people).returns(@people)
            @reflection = OpenStruct.new(:macro => :has_many)
            @instance.class.stubs(:reflect_on_association).returns(@reflection)
            @instance.configuration[:solr_includes] = {@assoc => {:type => :string}}
            @instance.solr_configuration.merge! :default_boost => 35.0
          end

          should "set the default name for the include, if none is configured" do
            @instance.configuration[:solr_includes] = {@assoc => {:type => :text}}
            doc = @instance.to_solr_doc
            assert_not_nil doc.fields.find {|f| f.name.to_s == "person_s"}
          end

          should "add the include alias" do
            @instance.configuration[:solr_includes] = {@assoc => {:as => :human, :type => :text}}
            doc = @instance.to_solr_doc
            assert_not_nil doc.fields.find {|f| f.name.to_s == "human_s"}
            assert_nil     doc.fields.find {|f| f.name.to_s == "person_s"}
          end

          should "add the include type" do
            @instance.configuration[:solr_includes] = {@assoc => {:type => :date}}
            @instance.expects(:get_solr_field_type).with(){|v| true}.at_least_once.returns('s')
            @instance.expects(:get_solr_field_type).with(:date).once.returns('d')
            doc = @instance.to_solr_doc
          end

          should "set the default boost for the include, if none is configured" do
            field = @instance.to_solr_doc.fields.find {|f| f.name.to_s == "person_s"}
            assert_equal 35.0, field.boost
          end

          should "add the include boost" do
            @instance.configuration[:solr_includes] = {@assoc => {:boost => 10.0}}
            field = @instance.to_solr_doc.fields.find {|f| f.name.to_s == "person_s"}
            assert_equal 10.0, field.boost
          end

          should "default to a field value with all association attributes" do
            field = @instance.to_solr_doc.fields.find {|f| f.name.to_s == "person_s"}
            @people.first.attributes.each do |attr, value|
              assert_match /#{value}/, field.value
            end
          end

          should "use a field value from an association method, if one is configured" do
            @instance.configuration[:solr_includes] = {@assoc => {:using => :name}}
            field = @instance.to_solr_doc.fields.find {|f| f.name.to_s == "person_s"}
            assert_equal @people.first.name, field.value
          end

          should "use a field value from a proc, if one is configured" do
            @instance.configuration[:solr_includes] = {@assoc => {:using => lambda{|r| r.name.reverse}}}
            field = @instance.to_solr_doc.fields.find {|f| f.name.to_s == "person_s"}
            assert_equal @people.first.name.reverse, field.value
          end

          should "join multiple values into a single field unless the :multivalued options is specified" do
            @instance.configuration[:solr_includes] = {@assoc => {:multivalued => :true}}
            second_person = {:name => 'Dean Venture', :address => 'Venture Compound'}
            @people << OpenStruct.new(second_person.merge(:attributes => second_person))
            fields = @instance.to_solr_doc.fields.select {|f| f.name.to_s == "person_s"}
            assert_equal @people.size, fields.size
          end

          should "include multiple values separately if the :multivalued options is specified"    do
            second_person = {:name => 'Dean Venture', :address => 'Venture Compound'}
            @people << OpenStruct.new(second_person.merge(:attributes => second_person))
            fields = @instance.to_solr_doc.fields.select {|f| f.name.to_s == "person_s"}
            assert_not_equal @people.size, fields.size
            assert_equal 1, fields.size
          end
        end
      end
    end
  end
end
