require 'spec_helper'

describe Mongo::Collection::View do

  let(:selector) do
    {}
  end

  let(:options) do
    {}
  end

  let(:view) do
    described_class.new(authorized_collection, selector, options)
  end

  after do
    authorized_collection.find.remove_many
  end

  pending '#parallel_scan'
  pending '#await_data'
  pending '#exhaust'
  pending '#no_cursor_timeout'
  pending '#partial'
  pending '#tailable'

  describe '#==' do

    context 'when the other object is not a collection view' do

      let(:other) { 'test' }

      it 'returns false' do
        expect(view).to_not eq(other)
      end
    end

    context 'when the views have the same collection, selector, and options' do

      let(:other) do
        described_class.new(authorized_collection, selector, options)
      end

      it 'returns true' do
        expect(view).to eq(other)
      end
    end

    context 'when two views have a different collection' do

      let(:other_collection) do
        authorized_client[:other]
      end

      let(:other) do
        described_class.new(other_collection, selector, options)
      end

      it 'returns false' do
        expect(view).not_to eq(other)
      end
    end

    context 'when two views have a different selector' do

      let(:other_selector) do
        { 'name' => 'Emily' }
      end

      let(:other) do
        described_class.new(authorized_collection, other_selector, options)
      end

      it 'returns false' do
        expect(view).not_to eq(other)
      end
    end

    context 'when two views have different options' do

      let(:other_options) do
        { 'limit' => 20 }
      end

      let(:other) do
        described_class.new(authorized_collection, selector, other_options)
      end

      it 'returns false' do
        expect(view).not_to eq(other)
      end
    end
  end

  describe 'copy' do

    let(:view_clone) do
      view.clone
    end

    it 'dups the options' do
      expect(view.options).not_to be(view_clone.options)
    end

    it 'dups the selector' do
      expect(view.selector).not_to be(view_clone.selector)
    end

    it 'references the same collection' do
      expect(view.collection).to be(view_clone.collection)
    end
  end

  describe '#each' do

    let(:documents) do
      (1..10).map{ |i| { field: "test#{i}" }}
    end

    before do
      authorized_collection.insert_many(documents)
    end

    after do
      authorized_collection.find.remove_many
    end

    context 'when sending the initial query' do

      context 'when limit is specified' do

        let(:options) do
          { :limit => 5 }
        end

        before do
          expect(Mongo::Operation::Read::Query).to receive(:new) do |spec|
            expect(spec[:options][:limit]).to eq(options[:limit])
          end.and_call_original
        end

        let(:returned) do
          view.to_a
        end

        it 'returns limited documents' do
          expect(returned.count).to eq(5)
        end

        it 'allows iteration of the documents' do
          returned.each do |doc|
            expect(doc).to have_key('field')
          end
        end
      end

      context 'when batch size is specified' do

        let(:options) do
          { :batch_size => 5 }
        end

        before do
          expect(Mongo::Operation::Read::Query).to receive(:new) do |spec|
            expect(spec[:options][:limit]).to eq(options[:batch_size])
          end.and_call_original
        end

        let(:returned) do
          view.to_a
        end

        it 'returns all the documents' do
          expect(returned.count).to eq(10)
        end

        it 'allows iteration of all documents' do
          returned.each do |doc|
            expect(doc).to have_key('field')
          end
        end
      end

      context 'when no limit is specified' do

        before do
          expect(Mongo::Operation::Read::Query).to receive(:new) do |spec|
            expect(spec[:options][:limit]).to eq(nil)
          end.and_call_original
        end

        let(:returned) do
          view.to_a
        end

        it 'returns all the documents' do
          expect(returned.count).to eq(10)
        end

        it 'allows iteration of all documents' do
          returned.each do |doc|
            expect(doc).to have_key('field')
          end
        end
      end

      context 'when batch size is greater than limit' do

        let(:options) do
          { :batch_size => 5, :limit => 3 }
        end

        before do
          expect(Mongo::Operation::Read::Query).to receive(:new) do |spec|
            expect(spec[:options][:limit]).to eq(options[:limit])
          end.and_call_original
        end

        let(:returned) do
          view.to_a
        end

        it 'returns the limit of documents' do
          expect(returned.count).to eq(3)
        end

        it 'allows iteration of the documents' do
          returned.each do |doc|
            expect(doc).to have_key('field')
          end
        end
      end

      context 'when limit is greater than batch size' do

        let(:options) do
          { :limit => 5, :batch_size => 3 }
        end

        before do
          expect(Mongo::Operation::Read::Query).to receive(:new) do |spec|
            expect(spec[:options][:limit]).to eq(options[:batch_size])
          end.and_call_original
        end

        let(:returned) do
          view.to_a
        end

        it 'returns the limit of documents' do
          expect(returned.count).to eq(5)
        end

        it 'allows iteration of the documents' do
          returned.each do |doc|
            expect(doc).to have_key('field')
          end
        end
      end

      context 'when the selector has special fields' do

        context 'when a snapshot option is provided' do

          let(:options) do
            { :snapshot => true }
          end

          before do
            expect(Mongo::Operation::Read::Query).to receive(:new) do |spec|
              expect(spec[:selector][:$query]).to eq(selector)
            end.and_call_original
          end

          it 'creates a special query selector' do
            view.each do |doc|
              expect(doc).to have_key('field')
            end
          end
        end

        context 'when a max_scan option is provided' do

          let(:options) do
            { :max_scan => 100 }
          end

          before do
            expect(Mongo::Operation::Read::Query).to receive(:new) do |spec|
              expect(spec[:selector][:$query]).to eq(selector)
            end.and_call_original
          end

          it 'creates a special query selector' do
            view.each do |doc|
              expect(doc).to have_key('field')
            end
          end
        end

        context 'when a show_disk_loc option is provided' do

          let(:options) do
            { :show_disk_loc => true }
          end

          before do
            expect(Mongo::Operation::Read::Query).to receive(:new) do |spec|
              expect(spec[:selector][:$query]).to eq(selector)
            end.and_call_original
          end

          it 'creates a special query selector' do
            view.each do |doc|
              expect(doc).to have_key('field')
              break
            end
          end
        end
      end

      context 'when sorting' do

        let(:options) do
          { :sort => {'x' => Mongo::Index::ASCENDING }}
        end

        before do
          expect(Mongo::Operation::Read::Query).to receive(:new) do |spec|
            expect(spec[:selector][:$query]).to eq(selector)
          end.and_call_original
        end

        it 'creates a special query selector' do
          view.each do |doc|
            expect(doc).to have_key('field')
          end
        end
      end

      context 'when providing a hint' do

        let(:options) do
          { :hint => { 'x' => Mongo::Index::ASCENDING }}
        end

        before do
          expect(Mongo::Operation::Read::Query).to receive(:new) do |spec|
            expect(spec[:selector][:$query]).to eq(selector)
          end.and_call_original
        end

        it'creates a special query selector' do
          view.each do |doc|
            expect(doc).to have_key('$err')
          end
        end
      end

      context 'when providing a comment' do

        let(:options) do
          { :comment => 'query1' }
        end

        before do
          expect(Mongo::Operation::Read::Query).to receive(:new) do |spec|
            expect(spec[:selector][:$query]).to eq(selector)
          end.and_call_original
        end

        it 'creates a special query selector' do
          view.each do |doc|
            expect(doc).to have_key('field')
          end
        end
      end

      context 'when the cluster is sharded' do

        before do
          expect(Mongo::Operation::Read::Query).to receive(:new) do |spec|
            expect(spec[:selector][:$query]).to eq(selector)
          end.and_call_original
        end

        it 'creates a special query selector' do
          view.each do |doc|
            expect(doc).to have_key('field')
          end
        end
      end
    end

    context 'when there are no special fields' do

      before do
        expect(Mongo::Operation::Read::Query).to receive(:new) do |spec|
          expect(spec[:selector]).to eq(selector)
        end.and_call_original
      end

      it 'creates a normal query spec' do
        view.each do |doc|
          expect(doc).to have_key('field')
        end
      end
    end

    pending 'when the cursor is created'
    pending 'when a block is provided'
    pending 'when a block is not provided'
  end

  describe '#hash' do

    let(:other) do
      described_class.new(authorized_collection, selector, options)
    end

    it 'returns a unique value based on collection, selector, options' do
      expect(view.hash).to eq(other.hash)
    end

    context 'when two views only have different collections' do

      let(:other_collection) do
        authorized_client[:other]
      end

      let(:other) do
        described_class.new(other_collection, selector, options)
      end

      it 'returns different hash values' do
        expect(view.hash).not_to eq(other.hash)
      end
    end

    context 'when two views only have different selectors' do

      let(:other_selector) do
        { 'name' => 'Emily' }
      end

      let(:other) do
        described_class.new(authorized_collection, other_selector, options)
      end

      it 'returns different hash values' do
        expect(view.hash).not_to eq(other.hash)
      end
    end

    context 'when two views only have different options' do

      let(:other_options) do
        { 'limit' => 20 }
      end

      let(:other) do
        described_class.new(authorized_collection, selector, other_options)
      end

      it 'returns different hash values' do
        expect(view.hash).not_to eq(other.hash)
      end
    end
  end

  describe '#initialize' do

    let(:options) do
      { :limit => 5 }
    end

    it 'sets the collection' do
      expect(view.collection).to eq(authorized_collection)
    end

    it 'sets the selector' do
      expect(view.selector).to eq(selector)
    end

    it 'dups the selector' do
      expect(view.selector).not_to be(selector)
    end

    it 'sets the options' do
      expect(view.options).to eq(options)
    end

    it 'dups the options' do
      expect(view.options).not_to be(options)
    end
  end

  describe '#inspect' do

    context 'when there is a namespace, selector, and options' do

      let(:options) do
        { :limit => 5 }
      end

      let(:selector) do
        { 'name' => 'Emily' }
      end

      it 'returns a string' do
        expect(view.inspect).to be_a(String)
      end

      it 'returns a string containing the collection namespace' do
        expect(view.inspect).to match(/.*#{authorized_collection.namespace}.*/)
      end

      it 'returns a string containing the selector' do
        expect(view.inspect).to match(/.*#{selector.inspect}.*/)
      end

      it 'returns a string containing the options' do
        expect(view.inspect).to match(/.*#{options.inspect}.*/)
      end
    end
  end
end
