# Copyright (C) 2009-2013 MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'test_helper'

class CursorUnitTest < Test::Unit::TestCase
  class Mongo::Cursor
    public :construct_query_spec
  end

  context "Cursor options" do
    setup do
      @logger     = mock()
      @logger.stubs(:debug)
      @connection = stub(:class => MongoClient, :logger => @logger,
        :slave_ok? => false, :read => :primary, :log_duration => false,
        :tag_sets => [], :acceptable_latency => 10)
      @db         = stub(:name => "testing", :slave_ok? => false,
        :connection => @connection, :read => :primary,
        :tag_sets => [], :acceptable_latency => 10)
      @collection = stub(:db => @db, :name => "items", :read => :primary,
        :tag_sets => [], :acceptable_latency => 10)
      @cursor     = Cursor.new(@collection)
    end

    should "set timeout" do
      assert @cursor.timeout
      assert @cursor.query_options_hash[:timeout]
    end

    should "set selector" do
      assert_equal({}, @cursor.selector)

      @cursor = Cursor.new(@collection, :selector => {:name => "Jones"})
      assert_equal({:name => "Jones"}, @cursor.selector)
      assert_equal({:name => "Jones"}, @cursor.query_options_hash[:selector])
    end

    should "set fields" do
      assert_nil @cursor.fields

      @cursor = Cursor.new(@collection, :fields => [:name, :date])
      assert_equal({:name => 1, :date => 1}, @cursor.fields)
      assert_equal({:name => 1, :date => 1}, @cursor.query_options_hash[:fields])
    end

    should "allow $meta projection operator" do
      assert_nil @cursor.fields

      @cursor = Cursor.new(@collection, :fields => [{ :score => { :$meta => 'textScore' } }])
      assert_equal({ :score  => { :$meta => 'textScore' } }, @cursor.fields)
      assert_equal({ :score  => { :$meta => 'textScore' } }, @cursor.query_options_hash[:fields])

      @cursor = Cursor.new(@collection, :fields => [:name, { :score => { :$meta => 'textScore' } }])
      assert_equal({ :name => 1, :score => { :$meta => 'textScore' } }, @cursor.fields)
      assert_equal({ :name => 1, :score => { :$meta => 'textScore' } }, @cursor.query_options_hash[:fields])
    end

    should "set mix fields 0 and 1" do
      assert_nil @cursor.fields

      @cursor = Cursor.new(@collection, :fields => {:name => 1, :date => 0})
      assert_equal({:name => 1, :date => 0}, @cursor.fields)
      assert_equal({:name => 1, :date => 0}, @cursor.query_options_hash[:fields])
    end

    should "set limit" do
      assert_equal 0, @cursor.limit

      @cursor = Cursor.new(@collection, :limit => 10)
      assert_equal 10, @cursor.limit
      assert_equal 10, @cursor.query_options_hash[:limit]
    end


    should "set skip" do
      assert_equal 0, @cursor.skip

      @cursor = Cursor.new(@collection, :skip => 5)
      assert_equal 5, @cursor.skip
      assert_equal 5, @cursor.query_options_hash[:skip]
    end

    should "set sort order" do
      assert_nil @cursor.order

      @cursor = Cursor.new(@collection, :order => "last_name")
      assert_equal "last_name", @cursor.order
      assert_equal "last_name", @cursor.query_options_hash[:order]
    end

    should "set hint" do
      assert_nil @cursor.hint

      @cursor = Cursor.new(@collection, :hint => "name")
      assert_equal "name", @cursor.hint
      assert_equal "name", @cursor.query_options_hash[:hint]
    end

    should "set comment" do
      assert_nil @cursor.comment

      @cursor = Cursor.new(@collection, :comment => "comment")
      assert_equal "comment", @cursor.comment
      assert_equal "comment", @cursor.query_options_hash[:comment]
    end

    should "cache full collection name" do
      assert_equal "testing.items", @cursor.full_collection_name
    end

    should "raise error when batch_size is 1" do
      e = assert_raise ArgumentError do
        @cursor.batch_size(1)
        end
        assert_equal "Invalid value for batch_size 1; must be 0 or > 1.", e.message
    end

    should "use the limit for batch size when it's smaller than the specified batch_size" do
      @cursor.limit(99)
      @cursor.batch_size(100)
      assert_equal 99, @cursor.batch_size
      end

    should "use the specified batch_size" do
      @cursor.batch_size(100)
      assert_equal 100, @cursor.batch_size
    end

    context "conected to mongos" do
      setup do
        @connection.stubs(:mongos?).returns(true)
        @tag_sets = [{:dc => "ny"}]
      end

      should "set $readPreference" do
        # secondary
        cursor = Cursor.new(@collection, { :read => :secondary })

        spec = cursor.construct_query_spec
        assert spec.has_key?('$readPreference')
        assert_equal 'secondary', spec['$readPreference'][:mode]
        assert !spec['$readPreference'].has_key?(:tags)

        # secondary preferred with tags
        cursor = Cursor.new(@collection, { :read => :secondary_preferred, :tag_sets => @tag_sets })

        spec = cursor.construct_query_spec
        assert spec.has_key?('$readPreference')
        assert_equal 'secondaryPreferred', spec['$readPreference'][:mode]
        assert_equal @tag_sets, spec['$readPreference'][:tags]

        # primary preferred
        cursor = Cursor.new(@collection, { :read => :primary_preferred })

        spec = cursor.construct_query_spec
        assert spec.has_key?('$readPreference')
        assert_equal 'primaryPreferred', spec['$readPreference'][:mode]
        assert !spec['$readPreference'].has_key?(:tags)

        # primary preferred with tags
        cursor = Cursor.new(@collection, { :read => :primary_preferred, :tag_sets => @tag_sets })

        spec = cursor.construct_query_spec
        assert spec.has_key?('$readPreference')
        assert_equal 'primaryPreferred', spec['$readPreference'][:mode]
        assert_equal @tag_sets, spec['$readPreference'][:tags]

        # nearest
        cursor = Cursor.new(@collection, { :read => :nearest })

        spec = cursor.construct_query_spec
        assert spec.has_key?('$readPreference')
        assert_equal 'nearest', spec['$readPreference'][:mode]
        assert !spec['$readPreference'].has_key?(:tags)

        # nearest with tags
        cursor = Cursor.new(@collection, { :read => :nearest, :tag_sets => @tag_sets })

        spec = cursor.construct_query_spec
        assert spec.has_key?('$readPreference')
        assert_equal 'nearest', spec['$readPreference'][:mode]
        assert_equal @tag_sets, spec['$readPreference'][:tags]
      end

      should "not set $readPreference" do
        # for primary
        cursor = Cursor.new(@collection, { :read => :primary, :tag_sets => @tag_sets })
        assert !cursor.construct_query_spec.has_key?('$readPreference')

        # for secondary_preferred with no tags
        cursor = Cursor.new(@collection, { :read => :secondary_preferred })
        assert !cursor.construct_query_spec.has_key?('$readPreference')

        cursor = Cursor.new(@collection, { :read => :secondary_preferred, :tag_sets => [] })
        assert !cursor.construct_query_spec.has_key?('$readPreference')

        cursor = Cursor.new(@collection, { :read => :secondary_preferred, :tag_sets => nil })
        assert !cursor.construct_query_spec.has_key?('$readPreference')
      end
    end

    context "not conected to mongos" do
      setup do
        @connection.stubs(:mongos?).returns(false)
      end

      should "not set $readPreference" do
        cursor = Cursor.new(@collection, { :read => :primary })
        assert !cursor.construct_query_spec.has_key?('$readPreference')

        cursor = Cursor.new(@collection, { :read => :primary_preferred })
        assert !cursor.construct_query_spec.has_key?('$readPreference')

        cursor = Cursor.new(@collection, { :read => :secondary })
        assert !cursor.construct_query_spec.has_key?('$readPreference')

        cursor = Cursor.new(@collection, { :read => :secondary_preferred })
        assert !cursor.construct_query_spec.has_key?('$readPreference')

        cursor = Cursor.new(@collection, { :read => :nearest })
        assert !cursor.construct_query_spec.has_key?('$readPreference')

        cursor = Cursor.new(@collection, { :read => :secondary , :tag_sets => @tag_sets})
        assert !cursor.construct_query_spec.has_key?('$readPreference')
      end
    end
  end

  context "Query fields" do
    setup do
      @logger     = mock()
      @logger.stubs(:debug)
      @connection = stub(:class => MongoClient, :logger => @logger, :slave_ok? => false,
        :log_duration => false, :tag_sets =>{}, :acceptable_latency => 10)
      @db = stub(:slave_ok? => true, :name => "testing", :connection => @connection,
        :tag_sets => {}, :acceptable_latency => 10)
      @collection = stub(:db => @db, :name => "items", :read => :primary,
        :tag_sets => {}, :acceptable_latency => 10)
    end

    should "when an array should return a hash with each key" do
      @cursor = Cursor.new(@collection, :fields => [:name, :age])
      result  = @cursor.fields
      assert_equal result.keys.sort{|a,b| a.to_s <=> b.to_s}, [:age, :name].sort{|a,b| a.to_s <=> b.to_s}
      assert result.values.all? {|v| v == 1}
    end

    should "when a string, return a hash with just the key" do
      @cursor = Cursor.new(@collection, :fields => "name")
      result  = @cursor.fields
      assert_equal result.keys.sort, ["name"]
      assert result.values.all? {|v| v == 1}
    end

    should "return nil when neither hash nor string nor symbol" do
      @cursor = Cursor.new(@collection, :fields => 1234567)
      assert_nil @cursor.fields
    end
  end

  context "counts" do
    setup do
      @logger     = mock()
      @logger.stubs(:debug)
      @connection = stub(:class => Connection, :logger => @logger,
        :slave_ok? => false, :read => :primary, :log_duration => false,
        :tag_sets => {}, :acceptable_latency => 10)
      @db         = stub(:name => "testing", :slave_ok? => false,
        :connection => @connection, :read => :primary,
        :tag_sets => {}, :acceptable_latency => 10)
      @collection = stub(:db => @db, :name => "items", :read => :primary,
        :tag_sets => {}, :acceptable_latency => 10)
      @cursor     = Cursor.new(@collection)
    end

    should "pass the comment parameter" do
      query = {:field => 7}
      @db.expects(:command).with({ 'count' => "items",
                                   'query' => query,
                                   'fields' => nil},
                                 { :read => :primary,
                                   :comment => "my comment"}).
        returns({'ok' => 1, 'n' => 1})
      assert_equal(1, Cursor.new(@collection, :selector => query, :comment => 'my comment').count())
    end
  end
end
