require 'spec_helper'

describe Mongo::Index::View do

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

  let(:options) do
    {}
  end

  describe '#drop_one' do

    let(:spec) do
      { another: -1 }
    end

    after do
      begin; view.drop_one('another_-1'); rescue; end
    end

    before do
      view.create_one(spec, unique: true)
    end

    context 'when provided a session' do

      let(:view_with_session) do
        described_class.new(authorized_collection, session: session)
      end

      let(:client) do
        authorized_client
      end

      let(:operation) do
        view_with_session.drop_one('another_-1')
      end

      let(:failed_operation) do
        view_with_session.drop_one('_another_-1')
      end

      it_behaves_like 'an operation using a session'
      it_behaves_like 'a failed operation using a session'
    end

    context 'when the index exists' do

      let(:result) do
        view.drop_one('another_-1')
      end

      it 'drops the index' do
        expect(result).to be_successful
      end
    end

    context 'when passing a * as the name' do

      it 'raises an exception' do
        expect {
          view.drop_one('*')
        }.to raise_error(Mongo::Error::MultiIndexDrop)
      end
    end

    context 'when the collection has a write concern' do

      let(:collection) do
        authorized_collection.with(write: INVALID_WRITE_CONCERN)
      end

      let(:view_with_write_concern) do
        described_class.new(collection)
      end

      let(:result) do
        view_with_write_concern.drop_one('another_-1')
      end

      context 'when the server accepts writeConcern for the dropIndexes operation', if: collation_enabled? do

        it 'applies the write concern' do
          expect {
            result
          }.to raise_exception(Mongo::Error::OperationFailure)
        end
      end

      context 'when the server does not accept writeConcern for the dropIndexes operation', unless: collation_enabled? do

        it 'does not apply the write concern' do
          expect(result).to be_successful
        end
      end
    end

    context 'when there are multiple indexes with the same key pattern', if: collation_enabled? do

      before do
        view.create_one({ random: 1 }, unique: true)
        view.create_one({ random: 1 },
                          name: 'random_1_with_collation',
                          unique: true,
                          collation: { locale: 'en_US', strength: 2 })
      end

      context 'when a name is supplied' do

        let!(:result) do
          view.drop_one('random_1_with_collation')
        end

        let(:index_names) do
          view.collect { |model| model['name'] }
        end

        it 'returns ok' do
          expect(result).to be_successful
        end

        it 'drops the correct index' do
          expect(index_names).not_to include('random_1_with_collation')
          expect(index_names).to include('random_1')
        end
      end
    end
  end

  describe '#drop_all' do

    let(:spec) do
      { another: -1 }
    end

    before do
      view.create_one(spec, unique: true)
    end

    context 'when indexes exists' do

      let(:result) do
        view.drop_all
      end

      it 'drops the index' do
        expect(result).to be_successful
      end

      context 'when provided a session' do

        let(:view_with_session) do
          described_class.new(authorized_collection, session: session)
        end

        let(:operation) do
          view_with_session.drop_all
        end

        let(:client) do
          authorized_client
        end

        it_behaves_like 'an operation using a session'
      end

      context 'when the collection has a write concern' do

        let(:collection) do
          authorized_collection.with(write: INVALID_WRITE_CONCERN)
        end

        let(:view_with_write_concern) do
          described_class.new(collection)
        end

        let(:result) do
          view_with_write_concern.drop_all
        end

        after do
          view.drop_all
        end

        context 'when the server accepts writeConcern for the dropIndexes operation', if: collation_enabled? do

          it 'applies the write concern' do
            expect {
              result
            }.to raise_exception(Mongo::Error::OperationFailure)
          end
        end

        context 'when the server does not accept writeConcern for the dropIndexes operation', unless: collation_enabled? do

          it 'does not apply the write concern' do
            expect(result).to be_successful
          end
        end
      end
    end
  end

  describe '#create_many' do

    context 'when the indexes are created' do

      context 'when passing multi-args' do

        context 'when the index creation is successful' do

          let!(:result) do
            view.create_many(
              { key: { random: 1 }, unique: true },
              { key: { testing: -1 }, unique: true }
            )
          end

          after do
            view.drop_one('random_1')
            view.drop_one('testing_-1')
          end

          it 'returns ok' do
            expect(result).to be_successful
          end

          context 'when provided a session' do

            let(:view_with_session) do
              described_class.new(authorized_collection, session: session)
            end

            let(:operation) do
              view_with_session.create_many(
                  { key: { random: 1 }, unique: true },
                  { key: { testing: -1 }, unique: true }
              )
            end

            let(:client) do
              authorized_client
            end

            let(:failed_operation) do
              view_with_session.create_many(
                  { key: { random: 1 }, invalid: true }
              )
            end

            it_behaves_like 'an operation using a session'
            it_behaves_like 'a failed operation using a session'
          end
        end

        context 'when collation is specified', if: collation_enabled? do

          let(:result) do
            view.create_many(
              { key: { random: 1 },
                unique: true,
                collation: { locale: 'en_US', strength: 2 } }
            )
          end

          after do
            begin; view.drop_one('random_1'); rescue; end
          end

          let(:index_info) do
            view.get('random_1')
          end

          context 'when the server supports collations', if: collation_enabled? do

            it 'returns ok' do
              expect(result).to be_successful
            end

            it 'applies the collation to the new index' do
              result
              expect(index_info['collation']).not_to be_nil
              expect(index_info['collation']['locale']).to eq('en_US')
              expect(index_info['collation']['strength']).to eq(2)
            end
          end

          context 'when the server does not support collations', unless: collation_enabled? do

            it 'raises an exception' do
              expect {
                result
              }.to raise_exception(Mongo::Error::UnsupportedCollation)
            end

            context 'when a String key is used' do

              let(:result) do
                view.create_many(
                  { key: { random: 1 },
                    unique: true,
                    'collation' => { locale: 'en_US', strength: 2 } }
                )
              end

              it 'raises an exception' do
                expect {
                  result
                }.to raise_exception(Mongo::Error::UnsupportedCollation)
              end
            end
          end
        end

        context 'when the collection has a write concern' do

          after do
            begin; view.drop_one('random_1'); rescue; end
            begin; view.drop_one('testing_-1'); rescue; end
          end

          let(:collection) do
            authorized_collection.with(write: INVALID_WRITE_CONCERN)
          end

          let(:view_with_write_concern) do
            described_class.new(collection)
          end

          let(:result) do
            view_with_write_concern.create_many(
                { key: { random: 1 }, unique: true },
                { key: { testing: -1 }, unique: true }
            )
          end

          context 'when the server accepts writeConcern for the createIndexes operation', if: collation_enabled? do

            it 'applies the write concern' do
              expect {
                result
              }.to raise_exception(Mongo::Error::OperationFailure)
            end
          end

          context 'when the server does not accept writeConcern for the createIndexes operation', unless: collation_enabled? do

            it 'does not apply the write concern' do
              expect(result).to be_successful
            end
          end
        end
      end

      context 'when passing an array' do

        context 'when the index creation is successful' do

          let!(:result) do
            view.create_many([
                                 { key: { random: 1 }, unique: true },
                                 { key: { testing: -1 }, unique: true }
                             ])
          end

          after do
            view.drop_one('random_1')
            view.drop_one('testing_-1')
          end

          it 'returns ok' do
            expect(result).to be_successful
          end

          context 'when provided a session' do

            let(:view_with_session) do
              described_class.new(authorized_collection, session: session)
            end

            let(:operation) do
              view_with_session.create_many([
                                             { key: { random: 1 }, unique: true },
                                             { key: { testing: -1 }, unique: true }
                                            ])
            end

            let(:failed_operation) do
              view_with_session.create_many([ { key: { random: 1 }, invalid: true }])
            end

            let(:client) do
              authorized_client
            end

            it_behaves_like 'an operation using a session'
            it_behaves_like 'a failed operation using a session'
          end
        end

        context 'when collation is specified' do

          let(:result) do
            view.create_many([
                                 { key: { random: 1 },
                                   unique: true,
                                   collation: { locale: 'en_US', strength: 2 }},
                             ])
          end

          let(:index_info) do
            view.get('random_1')
          end

          after do
            begin; view.drop_one('random_1'); rescue; end
          end

          context 'when the server supports collations', if: collation_enabled? do

            it 'returns ok' do
              expect(result).to be_successful
            end

            it 'applies the collation to the new index' do
              result
              expect(index_info['collation']).not_to be_nil
              expect(index_info['collation']['locale']).to eq('en_US')
              expect(index_info['collation']['strength']).to eq(2)
            end
          end

          context 'when the server does not support collations', unless: collation_enabled? do

            it 'raises an exception' do
              expect {
                result
              }.to raise_exception(Mongo::Error::UnsupportedCollation)
            end

            context 'when a String key is used' do

              let(:result) do
                view.create_many([
                                   { key: { random: 1 },
                                     unique: true,
                                     'collation' => { locale: 'en_US', strength: 2 }},
                                 ])
              end

              it 'raises an exception' do
                expect {
                  result
                }.to raise_exception(Mongo::Error::UnsupportedCollation)
              end
            end
          end
        end

        context 'when the collection has a write concern' do

          after do
            begin; view.drop_one('random_1'); rescue; end
            begin; view.drop_one('testing_-1'); rescue; end
          end

          let(:collection) do
            authorized_collection.with(write: INVALID_WRITE_CONCERN)
          end

          let(:view_with_write_concern) do
            described_class.new(collection)
          end

          let(:result) do
            view_with_write_concern.create_many([
                                 { key: { random: 1 }, unique: true },
                                 { key: { testing: -1 }, unique: true }
                             ])
          end

          context 'when the server accepts writeConcern for the createIndexes operation', if: collation_enabled? do

            it 'applies the write concern' do
              expect {
                result
              }.to raise_exception(Mongo::Error::OperationFailure)
            end
          end

          context 'when the server does not accept writeConcern for the createIndexes operation', unless: collation_enabled? do

            it 'does not apply the write concern' do
              expect(result).to be_successful
            end
          end
        end
      end

      context 'when index creation fails' do

        let(:spec) do
          { name: 1 }
        end

        before do
          view.create_one(spec, unique: true)
        end

        after do
          view.drop_one('name_1')
        end

        it 'raises an exception' do
          expect {
            view.create_many([{ key: { name: 1 }, unique: false }])
          }.to raise_error(Mongo::Error::OperationFailure)
        end
      end
    end
  end

  describe '#create_one' do

    context 'when the index is created' do

      let(:spec) do
        { random: 1 }
      end

      let(:result) do
        view.create_one(spec, unique: true)
      end

      it 'returns ok' do
        expect(result).to be_successful
      end

      context 'when provided a session' do

        let(:view_with_session) do
          described_class.new(authorized_collection, session: session)
        end

        let(:operation) do
          view_with_session.create_one(spec, unique: true)
        end

        let(:failed_operation) do
          view_with_session.create_one(spec, invalid: true)
        end

        let(:client) do
          authorized_client
        end

        it_behaves_like 'an operation using a session'
        it_behaves_like 'a failed operation using a session'
      end

      context 'when the collection has a write concern' do

        after do
          begin; view.drop_one('random_1'); rescue; end
        end

        let(:collection) do
          authorized_collection.with(write: INVALID_WRITE_CONCERN)
        end

        let(:view_with_write_concern) do
          described_class.new(collection)
        end

        let(:result) do
          view_with_write_concern.create_one(spec, unique: true)
        end

        context 'when the server accepts writeConcern for the createIndexes operation', if: collation_enabled? do

          it 'applies the write concern' do
            expect {
              result
            }.to raise_exception(Mongo::Error::OperationFailure)
          end
        end

        context 'when the server does not accept writeConcern for the createIndexes operation', unless: collation_enabled? do

          it 'does not apply the write concern' do
            expect(result).to be_successful
          end
        end
      end

      context 'when the index is created on an subdocument field' do

        after do
          begin; view.drop_one('random_1'); rescue; end
        end

        let(:spec) do
          { 'sub_document.random' => 1 }
        end

        let(:result) do
          view.create_one(spec, unique: true)
        end

        after do
          begin; view.drop_one('sub_document.random_1'); rescue; end
        end

        it 'returns ok' do
          expect(result).to be_successful
        end
      end
    end

    context 'when index creation fails' do

      let(:spec) do
        { name: 1 }
      end

      before do
        view.create_one(spec, unique: true)
      end

      after do
        view.drop_one('name_1')
      end

      it 'raises an exception' do
        expect {
          view.create_one(spec, unique: false)
        }.to raise_error(Mongo::Error::OperationFailure)
      end
    end

    context 'when providing an index name' do

      let(:spec) do
        { random: 1 }
      end

      let!(:result) do
        view.create_one(spec, unique: true, name: 'random_name')
      end

      after do
        view.drop_one('random_name')
      end

      it 'returns ok' do
        expect(result).to be_successful
      end

      it 'defines the index with the provided name' do
        expect(view.get('random_name')).to_not be_nil
      end
    end

    context 'when providing an invalid partial index filter', if: find_command_enabled? do

      it 'raises an exception' do
        expect {
          view.create_one({'x' => 1}, partial_filter_expression: 5)
        }.to raise_error(Mongo::Error::OperationFailure)
      end
    end

    context 'when providing a valid partial index filter', if: find_command_enabled? do

      let(:expression) do
        {'a' => {'$lte' => 1.5}}
      end

      let!(:result) do
        view.create_one({'x' => 1}, partial_filter_expression: expression)
      end

      let(:indexes) do
        authorized_collection.indexes.get('x_1')
      end

      after do
        view.drop_one('x_1')
      end

      it 'returns ok' do
        expect(result).to be_successful
      end

      it 'creates an index' do
        expect(indexes).to_not be_nil
      end

      it 'passes partialFilterExpression correctly' do
        expect(indexes[:partialFilterExpression]).to eq(expression)
      end
    end
  end

  describe '#get' do

    let(:spec) do
      { random: 1 }
    end

    let!(:result) do
      view.create_one(spec, unique: true, name: 'random_name')
    end

    after do
      begin; view.drop_one('random_name'); rescue; end
    end

    context 'when providing a name' do

      let(:index) do
        view.get('random_name')
      end

      it 'returns the index' do
        expect(index['name']).to eq('random_name')
      end
    end

    context 'when providing a spec' do

      let(:index) do
        view.get(random: 1)
      end

      it 'returns the index' do
        expect(index['name']).to eq('random_name')
      end
    end

    context 'when provided a session' do

      let(:view_with_session) do
        described_class.new(authorized_collection, session: session)
      end

      let(:operation) do
        view_with_session.get(random: 1)
      end

      let(:client) do
        authorized_client
      end

      it_behaves_like 'an operation using a session'
    end

    context 'when the index does not exist' do

      it 'returns nil' do
        expect(view.get(other: 1)).to be_nil
      end
    end
  end

  describe '#each' do

    context 'when the collection exists' do

      let(:spec) do
        { name: 1 }
      end

      before do
        view.create_one(spec, unique: true)
      end

      after do
        view.drop_one('name_1')
      end

      let(:indexes) do
        view.each
      end

      it 'returns all the indexes for the database' do
        expect(indexes.to_a.count).to eq(2)
      end
    end

    context 'when the collection does not exist' do

      let(:nonexistant_collection) do
        authorized_client[:not_a_collection]
      end

      let(:nonexistant_view) do
        described_class.new(nonexistant_collection)
      end

      it 'raises a nonexistant collection error', if: list_command_enabled? do
        expect {
          nonexistant_view.each.to_a
        }.to raise_error(Mongo::Error::OperationFailure)
      end
    end
  end

  describe '#normalize_models' do

    context 'when providing options' do

      let(:options) do
        {
          :key => { :name => 1 },
          :bucket_size => 5,
          :default_language => 'deutsch',
          :expire_after => 10,
          :language_override => 'language',
          :sphere_version => 1,
          :storage_engine => 'wiredtiger',
          :text_version => 2,
          :version => 1
        }
      end

      let(:models) do
        view.send(:normalize_models, [ options ], authorized_primary)
      end

      let(:expected) do
        {
          :key => { :name => 1 },
          :name => 'name_1',
          :bucketSize => 5,
          :default_language => 'deutsch',
          :expireAfterSeconds => 10,
          :language_override => 'language',
          :'2dsphereIndexVersion' => 1,
          :storageEngine => 'wiredtiger',
          :textIndexVersion => 2,
          :v => 1
        }
      end

      it 'maps the ruby options to the server options' do
        expect(models).to eq([ expected ])
      end

      context 'when using alternate names' do

        let(:extended_options) do
          options.merge!(expire_after_seconds: 5)
        end

        let(:extended_expected) do
          expected.tap { |exp| exp[:expireAfterSeconds] = 5 }
        end

        let(:models) do
          view.send(:normalize_models, [ extended_options ], authorized_primary)
        end

        it 'maps the ruby options to the server options' do
          expect(models).to eq([ extended_expected ])
        end
      end

      context 'when the server supports collations', if: collation_enabled? do

        let(:extended_options) do
          options.merge(:collation => { locale: 'en_US' } )
        end

        let(:models) do
          view.send(:normalize_models, [ extended_options ], authorized_primary)
        end

        let(:extended_expected) do
          expected.tap { |exp| exp[:collation] = { locale: 'en_US' } }
        end

        it 'maps the ruby options to the server options' do
          expect(models).to eq([ extended_expected ])
        end
      end
    end
  end
end
