# frozen_string_literal: true

require 'spec_helper'

describe Mongo::Collection do
  retry_test

  let(:subscriber) { Mrss::EventSubscriber.new }

  let(:client) do
    authorized_client.tap do |client|
      client.subscribe(Mongo::Monitoring::COMMAND, subscriber)
    end
  end

  let(:authorized_collection) { client['collection_spec'] }

  before do
    authorized_client['collection_spec'].drop
  end

  let(:collection_invalid_write_concern) do
    authorized_collection.client.with(write: INVALID_WRITE_CONCERN)[authorized_collection.name]
  end

  let(:collection_with_validator) do
    authorized_client[:validating]
  end

  describe '#find' do
    describe 'updating cluster time' do
      let(:operation) do
        client[TEST_COLL].find.first
      end

      let(:operation_with_session) do
        client[TEST_COLL].find({}, session: session).first
      end

      let(:second_operation) do
        client[TEST_COLL].find({}, session: session).first
      end

      it_behaves_like 'an operation updating cluster time'
    end

    context 'when provided a filter' do
      let(:view) do
        authorized_collection.find(name: 1)
      end

      it 'returns a authorized_collection view for the filter' do
        expect(view.filter).to eq('name' => 1)
      end
    end

    context 'when provided no filter' do
      let(:view) do
        authorized_collection.find
      end

      it 'returns a authorized_collection view with an empty filter' do
        expect(view.filter).to be_empty
      end
    end

    context 'when providing a bad filter' do
      let(:view) do
        authorized_collection.find('$or' => [])
      end

      it 'raises an exception when iterating' do
        expect do
          view.to_a
        end.to raise_exception(Mongo::Error::OperationFailure)
      end
    end

    context 'when iterating the authorized_collection view' do
      before do
        authorized_collection.insert_many([ { field: 'test1' }, { field: 'test2' } ])
      end

      let(:view) do
        authorized_collection.find
      end

      it 'iterates over the documents' do
        view.each do |document|
          expect(document).not_to be_nil
        end
      end
    end

    context 'when the user is not authorized' do
      require_auth

      let(:view) do
        unauthorized_collection.find
      end

      it 'iterates over the documents' do
        expect do
          view.each { |document| document }
        end.to raise_error(Mongo::Error::OperationFailure)
      end
    end

    context 'when documents contain potential error message fields' do
      [ 'errmsg', 'error', Mongo::Operation::Result::OK ].each do |field|
        context "when the document contains a '#{field}' field" do
          let(:value) do
            'testing'
          end

          let(:view) do
            authorized_collection.find
          end

          before do
            authorized_collection.insert_one({ field => value })
          end

          it 'iterates over the documents' do
            view.each do |document|
              expect(document[field]).to eq(value)
            end
          end
        end
      end
    end

    context 'when provided options' do
      let(:view) do
        authorized_collection.find({}, options)
      end

      context 'when a session is provided' do
        let(:operation) do
          authorized_collection.find({}, session: session).to_a
        end

        let(:session) do
          authorized_client.start_session
        end

        let(:failed_operation) do
          client[authorized_collection.name].find({ '$._id' => 1 }, session: session).to_a
        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 'session id' do
        require_topology :replica_set, :sharded

        let(:options) do
          { session: session }
        end

        let(:session) do
          client.start_session
        end

        let(:view) do
          Mongo::Collection::View.new(client[TEST_COLL], selector, view_options)
        end

        let(:command) do
          client[TEST_COLL].find({}, session: session).explain
          subscriber.started_events.find { |c| c.command_name == 'explain' }.command
        end

        it 'sends the session id' do
          expect(command['lsid']).to eq(session.session_id)
        end
      end

      context 'when a session supporting causal consistency is used' do
        let(:operation) do
          collection.find({}, session: session).to_a
        end

        let(:command) do
          operation
          subscriber.started_events.find { |cmd| cmd.command_name == 'find' }.command
        end

        it_behaves_like 'an operation supporting causally consistent reads'
      end

      context 'when provided :allow_partial_results' do
        let(:options) do
          { allow_partial_results: true }
        end

        it 'returns a view with :allow_partial_results set' do
          expect(view.options[:allow_partial_results]).to be(options[:allow_partial_results])
        end
      end

      context 'when provided :batch_size' do
        let(:options) do
          { batch_size: 100 }
        end

        it 'returns a view with :batch_size set' do
          expect(view.options[:batch_size]).to eq(options[:batch_size])
        end
      end

      context 'when provided :comment' do
        let(:options) do
          { comment: 'slow query' }
        end

        it 'returns a view with :comment set' do
          expect(view.modifiers[:$comment]).to eq(options[:comment])
        end
      end

      context 'when provided :cursor_type' do
        let(:options) do
          { cursor_type: :tailable }
        end

        it 'returns a view with :cursor_type set' do
          expect(view.options[:cursor_type]).to eq(options[:cursor_type])
        end
      end

      context 'when provided :max_time_ms' do
        let(:options) do
          { max_time_ms: 500 }
        end

        it 'returns a view with :max_time_ms set' do
          expect(view.modifiers[:$maxTimeMS]).to eq(options[:max_time_ms])
        end
      end

      context 'when provided :modifiers' do
        let(:options) do
          { modifiers: { '$orderby' => Mongo::Index::ASCENDING } }
        end

        it 'returns a view with modifiers set' do
          expect(view.modifiers).to eq(options[:modifiers])
        end

        it 'dups the modifiers hash' do
          expect(view.modifiers).not_to be(options[:modifiers])
        end
      end

      context 'when provided :no_cursor_timeout' do
        let(:options) do
          { no_cursor_timeout: true }
        end

        it 'returns a view with :no_cursor_timeout set' do
          expect(view.options[:no_cursor_timeout]).to eq(options[:no_cursor_timeout])
        end
      end

      context 'when provided :oplog_replay' do
        let(:options) do
          { oplog_replay: false }
        end

        it 'returns a view with :oplog_replay set' do
          expect(view.options[:oplog_replay]).to eq(options[:oplog_replay])
        end
      end

      context 'when provided :projection' do
        let(:options) do
          { projection: { 'x' => 1 } }
        end

        it 'returns a view with :projection set' do
          expect(view.options[:projection]).to eq(options[:projection])
        end
      end

      context 'when provided :skip' do
        let(:options) do
          { skip: 5 }
        end

        it 'returns a view with :skip set' do
          expect(view.options[:skip]).to eq(options[:skip])
        end
      end

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

        it 'returns a view with :sort set' do
          expect(view.modifiers[:$orderby]).to eq(options[:sort])
        end
      end

      context 'when provided :collation' do
        let(:options) do
          { collation: { 'locale' => 'en_US' } }
        end

        it 'returns a view with :collation set' do
          expect(view.options[:collation]).to eq(options[:collation])
        end
      end
    end
  end

  describe '#insert_many' do
    let(:result) do
      authorized_collection.insert_many([ { name: 'test1' }, { name: 'test2' } ])
    end

    it 'inserts the documents into the collection' do
      expect(result.inserted_count).to eq(2)
    end

    it 'contains the ids in the result' do
      expect(result.inserted_ids.size).to eq(2)
    end

    context 'when an enumerable is used instead of an array' do
      context 'when the enumerable is not empty' do
        let(:source_data) do
          [ { name: 'test1' }, { name: 'test2' } ]
        end

        let(:result) do
          authorized_collection.insert_many(source_data.lazy)
        end

        it 'accepts them without raising an error' do
          expect { result }.not_to raise_error
          expect(result.inserted_count).to eq(source_data.size)
        end
      end

      context 'when the enumerable is empty' do
        let(:source_data) do
          []
        end

        let(:result) do
          authorized_collection.insert_many(source_data.lazy)
        end

        it 'raises ArgumentError' do
          expect do
            result
          end.to raise_error(ArgumentError, /Bulk write requests cannot be empty/)
        end
      end
    end

    context 'when a session is provided' do
      let(:session) do
        authorized_client.start_session
      end

      let(:operation) do
        authorized_collection.insert_many([ { name: 'test1' }, { name: 'test2' } ], session: session)
      end

      let(:failed_operation) do
        authorized_collection.insert_many([ { _id: 'test1' }, { _id: 'test1' } ], session: session)
      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 unacknowledged writes is used with an explicit session' do
      let(:collection_with_unacknowledged_write_concern) do
        authorized_collection.with(write: { w: 0 })
      end

      let(:operation) do
        collection_with_unacknowledged_write_concern.insert_many([ { name: 'test1' }, { name: 'test2' } ],
                                                                 session: session)
      end

      it_behaves_like 'an explicit session with an unacknowledged write'
    end

    context 'when unacknowledged writes is used with an implicit session' do
      let(:collection_with_unacknowledged_write_concern) do
        client.with(write: { w: 0 })[TEST_COLL]
      end

      let(:operation) do
        collection_with_unacknowledged_write_concern.insert_many([ { name: 'test1' }, { name: 'test2' } ])
      end

      it_behaves_like 'an implicit session with an unacknowledged write'
    end

    context 'when a document contains dotted keys' do
      let(:docs) do
        [ { 'first.name' => 'test1' }, { name: 'test2' } ]
      end

      let(:view) { authorized_collection.find({}, { sort: { name: 1 } }) }

      it 'inserts the documents correctly' do
        expect do
          authorized_collection.insert_many(docs)
        end.not_to raise_error

        expect(view.count).to eq(2)
        expect(view.first['first.name']).to eq('test1')
        expect(view.to_a[1]['name']).to eq('test2')
      end
    end

    context 'when the client has a custom id generator' do
      let(:generator) do
        Class.new do
          def generate
            1
          end
        end.new
      end

      let(:custom_client) do
        authorized_client.with(id_generator: generator)
      end

      let(:custom_collection) do
        custom_client['custom_id_generator_test_collection']
      end

      before do
        custom_collection.delete_many
        custom_collection.insert_many([ { name: 'testing' } ])
        expect(custom_collection.count).to eq(1)
      end

      it 'inserts with the custom id' do
        expect(custom_collection.count).to eq(1)
        expect(custom_collection.find.first[:_id]).to eq(1)
      end
    end

    context 'when the inserts fail' do
      let(:result) do
        authorized_collection.insert_many([ { _id: 1 }, { _id: 1 } ])
      end

      it 'raises an BulkWriteError' do
        expect do
          result
        end.to raise_exception(Mongo::Error::BulkWriteError)
      end
    end

    context 'when the documents exceed the max bson size' do
      let(:documents) do
        [ { '_id' => 1, 'name' => '1' * 17_000_000 } ]
      end

      it 'raises a MaxBSONSize error' do
        expect do
          authorized_collection.insert_many(documents)
        end.to raise_error(Mongo::Error::MaxBSONSize)
      end
    end

    context 'when the documents are sent with OP_MSG' do
      let(:documents) do
        [ { '_id' => 1, 'name' => '1' * 16_777_191 }, { '_id' => 'y' } ]
      end

      before do
        authorized_collection.insert_many(documents)
      end

      let(:insert_events) do
        subscriber.started_events.select { |e| e.command_name == 'insert' }
      end

      it 'sends the documents in one OP_MSG' do
        expect(insert_events.size).to eq(1)
        expect(insert_events[0].command['documents']).to eq(documents)
      end
    end

    context 'when collection has a validator' do
      around do |spec|
        authorized_client[:validating].drop
        authorized_client[:validating,
                          validator: { a: { '$exists' => true } }].tap do |c|
          c.create
        end
        spec.run
        collection_with_validator.drop
      end

      context 'when the document is valid' do
        let(:result) do
          collection_with_validator.insert_many([ { a: 1 }, { a: 2 } ])
        end

        it 'inserts successfully' do
          expect(result.inserted_count).to eq(2)
        end
      end

      context 'when the document is invalid' do
        context 'when bypass_document_validation is not set' do
          let(:result2) do
            collection_with_validator.insert_many([ { x: 1 }, { x: 2 } ])
          end

          it 'raises a BulkWriteError' do
            expect do
              result2
            end.to raise_exception(Mongo::Error::BulkWriteError)
          end
        end

        context 'when bypass_document_validation is true' do
          let(:result3) do
            collection_with_validator.insert_many(
              [ { x: 1 }, { x: 2 } ], bypass_document_validation: true
            )
          end

          it 'inserts successfully' do
            expect(result3.inserted_count).to eq(2)
          end
        end
      end
    end

    context 'when unacknowledged writes is used' do
      let(:collection_with_unacknowledged_write_concern) do
        authorized_collection.with(write: { w: 0 })
      end

      let(:result) do
        collection_with_unacknowledged_write_concern.insert_many([ { _id: 1 }, { _id: 1 } ])
      end

      it 'does not raise an exception' do
        expect(result.inserted_count).to be(0)
      end
    end

    context 'when various options passed in' do
      # w: 2 requires a replica set
      require_topology :replica_set

      let(:session) do
        authorized_client.start_session
      end

      let(:events) do
        subscriber.command_started_events('insert')
      end

      let(:collection) do
        authorized_collection.with(write_concern: { w: 2 })
      end

      let!(:command) do
        Utils.get_command_event(authorized_client, 'insert') do |_client|
          collection.insert_many([ { name: 'test1' }, { name: 'test2' } ], session: session,
                                                                           write_concern: { w: 1 }, bypass_document_validation: true)
        end.command
      end

      it 'inserts many successfully with correct options sent to server' do
        expect(events.length).to eq(1)
        expect(command[:writeConcern]).not_to be_nil
        expect(command[:writeConcern][:w]).to eq(1)
        expect(command[:bypassDocumentValidation]).to be(true)
      end
    end
  end

  describe '#insert_one' do
    let(:result) do
      authorized_collection.insert_one({ name: 'testing' })
    end

    describe 'updating cluster time' do
      let(:operation) do
        client[TEST_COLL].insert_one({ name: 'testing' })
      end

      let(:operation_with_session) do
        client[TEST_COLL].insert_one({ name: 'testing' }, session: session)
      end

      let(:second_operation) do
        client[TEST_COLL].insert_one({ name: 'testing' }, session: session)
      end

      it_behaves_like 'an operation updating cluster time'
    end

    it 'inserts the document into the collection' do
      expect(result.written_count).to eq(1)
    end

    it 'contains the id in the result' do
      expect(result.inserted_id).not_to be_nil
    end

    context 'when a session is provided' do
      let(:session) do
        authorized_client.start_session
      end

      let(:operation) do
        authorized_collection.insert_one({ name: 'testing' }, session: session)
      end

      let(:failed_operation) do
        authorized_collection.insert_one({ _id: 'testing' })
        authorized_collection.insert_one({ _id: 'testing' }, session: session)
      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 unacknowledged writes is used with an explicit session' do
      let(:collection_with_unacknowledged_write_concern) do
        authorized_collection.with(write: { w: 0 })
      end

      let(:operation) do
        collection_with_unacknowledged_write_concern.insert_one({ name: 'testing' }, session: session)
      end

      it_behaves_like 'an explicit session with an unacknowledged write'
    end

    context 'when unacknowledged writes is used with an implicit session' do
      let(:collection_with_unacknowledged_write_concern) do
        client.with(write: { w: 0 })[TEST_COLL]
      end

      let(:operation) do
        collection_with_unacknowledged_write_concern.insert_one({ name: 'testing' })
      end

      it_behaves_like 'an implicit session with an unacknowledged write'
    end

    context 'when various options passed in' do
      let(:session) do
        authorized_client.start_session
      end

      let(:events) do
        subscriber.command_started_events('insert')
      end

      let(:collection) do
        authorized_collection.with(write_concern: { w: 3 })
      end

      let!(:command) do
        Utils.get_command_event(authorized_client, 'insert') do |_client|
          collection.insert_one({ name: 'test1' }, session: session, write_concern: { w: 1 },
                                                   bypass_document_validation: true)
        end.command
      end

      it 'inserts one successfully with correct options sent to server' do
        expect(events.length).to eq(1)
        expect(command[:writeConcern]).not_to be_nil
        expect(command[:writeConcern][:w]).to eq(1)
        expect(command[:bypassDocumentValidation]).to be(true)
      end
    end

    context 'when the document contains dotted keys' do
      let(:doc) do
        { 'testing.test' => 'value' }
      end

      it 'inserts the document correctly' do
        expect do
          authorized_collection.insert_one(doc)
        end.not_to raise_error

        expect(authorized_collection.count).to eq(1)
        expect(authorized_collection.find.first['testing.test']).to eq('value')
      end
    end

    context 'when the document is nil' do
      let(:result) do
        authorized_collection.insert_one(nil)
      end

      it 'raises an ArgumentError' do
        expect do
          result
        end.to raise_error(ArgumentError, 'Document to be inserted cannot be nil')
      end
    end

    context 'when the insert fails' do
      let(:result) do
        authorized_collection.insert_one(_id: 1)
        authorized_collection.insert_one(_id: 1)
      end

      it 'raises an OperationFailure' do
        expect do
          result
        end.to raise_exception(Mongo::Error::OperationFailure)
      end
    end

    context 'when the client has a custom id generator' do
      let(:generator) do
        Class.new do
          def generate
            1
          end
        end.new
      end

      let(:custom_client) do
        authorized_client.with(id_generator: generator)
      end

      let(:custom_collection) do
        custom_client[TEST_COLL]
      end

      before do
        custom_collection.delete_many
        custom_collection.insert_one({ name: 'testing' })
      end

      it 'inserts with the custom id' do
        expect(custom_collection.find.first[:_id]).to eq(1)
      end
    end

    context 'when collection has a validator' do
      around do |spec|
        authorized_client[:validating,
                          validator: { a: { '$exists' => true } }].tap do |c|
          c.create
        end
        spec.run
        collection_with_validator.drop
      end

      context 'when the document is valid' do
        let(:result) do
          collection_with_validator.insert_one({ a: 1 })
        end

        it 'inserts successfully' do
          expect(result.written_count).to eq(1)
        end
      end

      context 'when the document is invalid' do
        context 'when bypass_document_validation is not set' do
          let(:result2) do
            collection_with_validator.insert_one({ x: 1 })
          end

          it 'raises a OperationFailure' do
            expect do
              result2
            end.to raise_exception(Mongo::Error::OperationFailure)
          end
        end

        context 'when bypass_document_validation is true' do
          let(:result3) do
            collection_with_validator.insert_one(
              { x: 1 }, bypass_document_validation: true
            )
          end

          it 'inserts successfully' do
            expect(result3.written_count).to eq(1)
          end
        end
      end
    end
  end

  describe '#bulk_write' do
    context 'when various options passed in' do
      require_topology :replica_set

      let(:requests) do
        [
          { insert_one: { name: 'anne' } },
          { insert_one: { name: 'bob' } },
          { insert_one: { name: 'charlie' } }
        ]
      end

      let(:session) do
        authorized_client.start_session
      end

      let!(:command) do
        Utils.get_command_event(authorized_client, 'insert') do |_client|
          collection.bulk_write(requests, session: session, write_concern: { w: 1 },
                                          bypass_document_validation: true)
        end.command
      end

      let(:events) do
        subscriber.command_started_events('insert')
      end

      let(:collection) do
        authorized_collection.with(write_concern: { w: 2 })
      end

      it 'inserts successfully with correct options sent to server' do
        expect(collection.count).to eq(3)
        expect(events.length).to eq(1)
        expect(command[:writeConcern]).not_to be_nil
        expect(command[:writeConcern][:w]).to eq(1)
        expect(command[:bypassDocumentValidation]).to eq(true)
      end
    end
  end

  describe '#aggregate' do
    describe 'updating cluster time' do
      let(:operation) do
        client[TEST_COLL].aggregate([]).first
      end

      let(:operation_with_session) do
        client[TEST_COLL].aggregate([], session: session).first
      end

      let(:second_operation) do
        client[TEST_COLL].aggregate([], session: session).first
      end

      it_behaves_like 'an operation updating cluster time'
    end

    context 'when a session supporting causal consistency is used' do
      let(:operation) do
        collection.aggregate([], session: session).first
      end

      let(:command) do
        operation
        subscriber.started_events.find { |cmd| cmd.command_name == 'aggregate' }.command
      end

      it_behaves_like 'an operation supporting causally consistent reads'
    end

    it 'returns an Aggregation object' do
      expect(authorized_collection.aggregate([])).to be_a(Mongo::Collection::View::Aggregation)
    end

    context 'when options are provided' do
      let(:options) do
        { allow_disk_use: true, bypass_document_validation: true }
      end

      it 'sets the options on the Aggregation object' do
        expect(authorized_collection.aggregate([], options).options).to eq(BSON::Document.new(options))
      end

      context 'when the :comment option is provided' do
        let(:options) do
          { comment: 'testing' }
        end

        it 'sets the options on the Aggregation object' do
          expect(authorized_collection.aggregate([], options).options).to eq(BSON::Document.new(options))
        end
      end

      context 'when a session is provided' do
        let(:session) do
          authorized_client.start_session
        end

        let(:operation) do
          authorized_collection.aggregate([], session: session).to_a
        end

        let(:failed_operation) do
          authorized_collection.aggregate([ { '$invalid' => 1 } ], session: session).to_a
        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 a hint is provided' do
        let(:options) do
          { 'hint' => { 'y' => 1 } }
        end

        it 'sets the options on the Aggregation object' do
          expect(authorized_collection.aggregate([], options).options).to eq(options)
        end
      end

      context 'when collation is provided' do
        before do
          authorized_collection.insert_many([ { name: 'bang' }, { name: 'bang' } ])
        end

        let(:pipeline) do
          [ { '$match' => { 'name' => 'BANG' } } ]
        end

        let(:options) do
          { collation: { locale: 'en_US', strength: 2 } }
        end

        let(:result) do
          authorized_collection.aggregate(pipeline, options).collect { |doc| doc['name'] }
        end

        context 'when the server selected supports collations' do
          it 'applies the collation' do
            expect(result).to eq(%w[bang bang])
          end
        end
      end
    end
  end

  describe '#count_documents' do
    before do
      authorized_collection.delete_many
    end

    context 'no argument provided' do
      context 'when collection is empty' do
        it 'returns 0 matching documents' do
          expect(authorized_collection.count_documents).to eq(0)
        end
      end

      context 'when collection is not empty' do
        let(:documents) do
          documents = []
          1.upto(10) do |index|
            documents << { key: 'a', _id: "in#{index}" }
          end
          documents
        end

        before do
          authorized_collection.insert_many(documents)
        end

        it 'returns 10 matching documents' do
          expect(authorized_collection.count_documents).to eq(10)
        end
      end
    end

    context 'when transactions are enabled' do
      require_transaction_support

      before do
        # Ensure that the collection is created
        authorized_collection.insert_one(x: 1)
        authorized_collection.delete_many({})
      end

      let(:session) do
        authorized_client.start_session
      end

      it 'successfully starts a transaction and executes a transaction' do
        session.start_transaction
        expect(
          session.instance_variable_get(:@state)
        ).to eq(Mongo::Session::STARTING_TRANSACTION_STATE)

        expect(authorized_collection.count_documents({}, { session: session })).to eq(0)
        expect(
          session.instance_variable_get(:@state)
        ).to eq(Mongo::Session::TRANSACTION_IN_PROGRESS_STATE)

        authorized_collection.insert_one({ x: 1 }, { session: session })
        expect(authorized_collection.count_documents({}, { session: session })).to eq(1)

        session.commit_transaction
        expect(
          session.instance_variable_get(:@state)
        ).to eq(Mongo::Session::TRANSACTION_COMMITTED_STATE)
      end
    end
  end

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

    before do
      authorized_collection.insert_many(documents)
    end

    it 'returns an integer count' do
      expect(authorized_collection.count).to eq(10)
    end

    context 'when options are provided' do
      it 'passes the options to the count' do
        expect(authorized_collection.count({}, limit: 5)).to eq(5)
      end

      context 'when a session is provided' do
        let(:session) do
          authorized_client.start_session
        end

        let(:operation) do
          authorized_collection.count({}, session: session)
        end

        let(:failed_operation) do
          authorized_collection.count({ '$._id' => 1 }, session: session)
        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 a session supporting causal consistency is used' do
        let(:operation) do
          collection.count({}, session: session)
        end

        let(:command) do
          operation
          subscriber.started_events.find { |cmd| cmd.command_name == 'count' }.command
        end

        it_behaves_like 'an operation supporting causally consistent reads'
      end

      context 'when a collation is specified' do
        let(:selector) do
          { name: 'BANG' }
        end

        let(:result) do
          authorized_collection.count(selector, options)
        end

        before do
          authorized_collection.insert_one(name: 'bang')
        end

        let(:options) do
          { collation: { locale: 'en_US', strength: 2 } }
        end

        context 'when the server selected supports collations' do
          it 'applies the collation to the count' do
            expect(result).to eq(1)
          end
        end
      end
    end
  end

  describe '#distinct' do
    let(:documents) do
      (1..3).map { |i| { field: "test#{i}" } }
    end

    before do
      authorized_collection.insert_many(documents)
    end

    it 'returns the distinct values' do
      expect(authorized_collection.distinct(:field).sort).to eq(%w[test1 test2 test3])
    end

    context 'when a selector is provided' do
      it 'returns the distinct values' do
        expect(authorized_collection.distinct(:field, field: 'test1')).to eq([ 'test1' ])
      end
    end

    context 'when options are provided' do
      it 'passes the options to the distinct command' do
        expect(authorized_collection.distinct(:field, {}, max_time_ms: 100).sort).to eq(%w[test1 test2 test3])
      end

      context 'when a session is provided' do
        let(:session) do
          authorized_client.start_session
        end

        let(:operation) do
          authorized_collection.distinct(:field, {}, session: session)
        end

        let(:failed_operation) do
          authorized_collection.distinct(:field, { '$._id' => 1 }, session: session)
        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 a session supporting causal consistency is used' do
      let(:operation) do
        collection.distinct(:field, {}, session: session)
      end

      let(:command) do
        operation
        subscriber.started_events.find { |cmd| cmd.command_name == 'distinct' }.command
      end

      it_behaves_like 'an operation supporting causally consistent reads'
    end

    context 'when a collation is specified' do
      let(:result) do
        authorized_collection.distinct(:name, {}, options)
      end

      before do
        authorized_collection.insert_one(name: 'bang')
        authorized_collection.insert_one(name: 'BANG')
      end

      let(:options) do
        { collation: { locale: 'en_US', strength: 2 } }
      end

      context 'when the server selected supports collations' do
        it 'applies the collation to the distinct' do
          expect(result).to eq([ 'bang' ])
        end
      end
    end

    context 'when a collation is not specified' do
      let(:result) do
        authorized_collection.distinct(:name)
      end

      before do
        authorized_collection.insert_one(name: 'bang')
        authorized_collection.insert_one(name: 'BANG')
      end

      it 'does not apply the collation to the distinct' do
        expect(result).to match_array(%w[bang BANG])
      end
    end
  end

  describe '#delete_one' do
    context 'when a selector was provided' do
      let(:selector) do
        { field: 'test1' }
      end

      before do
        authorized_collection.insert_many([
                                            { field: 'test1' },
                                            { field: 'test1' },
                                            { field: 'test1' }
                                          ])
      end

      let(:response) do
        authorized_collection.delete_one(selector)
      end

      it 'deletes the first matching document in the collection' do
        expect(response.deleted_count).to eq(1)
      end
    end

    context 'when no selector was provided' do
      before do
        authorized_collection.insert_many([ { field: 'test1' }, { field: 'test2' } ])
      end

      let(:response) do
        authorized_collection.delete_one
      end

      it 'deletes the first document in the collection' do
        expect(response.deleted_count).to eq(1)
      end
    end

    context 'when the delete fails' do
      require_topology :single

      let(:result) do
        collection_invalid_write_concern.delete_one
      end

      it 'raises an OperationFailure' do
        expect do
          result
        end.to raise_exception(Mongo::Error::OperationFailure)
      end
    end

    context 'when a session is provided' do
      let(:session) do
        authorized_client.start_session
      end

      let(:operation) do
        authorized_collection.delete_one({}, session: session)
      end

      let(:failed_operation) do
        authorized_collection.delete_one({ '$._id' => 1 }, session: session)
      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 unacknowledged writes is used' do
      let(:collection_with_unacknowledged_write_concern) do
        authorized_collection.with(write: { w: 0 })
      end

      let(:operation) do
        collection_with_unacknowledged_write_concern.delete_one({}, session: session)
      end

      it_behaves_like 'an explicit session with an unacknowledged write'
    end

    context 'when unacknowledged writes is used with an implicit session' do
      let(:collection_with_unacknowledged_write_concern) do
        client.with(write: { w: 0 })[TEST_COLL]
      end

      let(:operation) do
        collection_with_unacknowledged_write_concern.delete_one
      end

      it_behaves_like 'an implicit session with an unacknowledged write'
    end

    context 'when a collation is provided' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.delete_one(selector, options)
      end

      before do
        authorized_collection.insert_one(name: 'bang')
      end

      let(:options) do
        { collation: { locale: 'en_US', strength: 2 } }
      end

      context 'when the server selected supports collations' do
        it 'applies the collation' do
          expect(result.written_count).to eq(1)
          expect(authorized_collection.find(name: 'bang').count).to eq(0)
        end

        context 'when unacknowledged writes is used' do
          let(:collection_with_unacknowledged_write_concern) do
            authorized_collection.with(write: { w: 0 })
          end

          let(:result) do
            collection_with_unacknowledged_write_concern.delete_one(selector, options)
          end

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

          context 'when a String key is used' do
            let(:options) do
              { 'collation' => { locale: 'en_US', strength: 2 } }
            end

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

    context 'when collation is not specified' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.delete_one(selector)
      end

      before do
        authorized_collection.insert_one(name: 'bang')
      end

      it 'does not apply the collation' do
        expect(result.written_count).to eq(0)
        expect(authorized_collection.find(name: 'bang').count).to eq(1)
      end
    end

    context 'when various options passed in' do
      # w: 2 requires a replica set
      require_topology :replica_set

      before do
        authorized_collection.insert_many([ { name: 'test1' }, { name: 'test2' } ])
      end

      let(:selector) do
        { name: 'test2' }
      end

      let(:session) do
        authorized_client.start_session
      end

      let(:events) do
        subscriber.command_started_events('delete')
      end

      let(:collection) do
        authorized_collection.with(write_concern: { w: 2 })
      end

      let!(:command) do
        Utils.get_command_event(authorized_client, 'delete') do |_client|
          collection.delete_one(selector, session: session, write_concern: { w: 1 },
                                          bypass_document_validation: true)
        end.command
      end

      it 'deletes one successfully with correct options sent to server' do
        expect(events.length).to eq(1)
        expect(command[:writeConcern]).not_to be_nil
        expect(command[:writeConcern][:w]).to eq(1)
        expect(command[:bypassDocumentValidation]).to eq(true)
      end
    end
  end

  describe '#delete_many' do
    before do
      authorized_collection.insert_many([ { field: 'test1' }, { field: 'test2' } ])
    end

    context 'when a selector was provided' do
      let(:selector) do
        { field: 'test1' }
      end

      it 'deletes the matching documents in the collection' do
        expect(authorized_collection.delete_many(selector).deleted_count).to eq(1)
      end
    end

    context 'when no selector was provided' do
      it 'deletes all the documents in the collection' do
        expect(authorized_collection.delete_many.deleted_count).to eq(2)
      end
    end

    context 'when the deletes fail' do
      require_topology :single

      let(:result) do
        collection_invalid_write_concern.delete_many
      end

      it 'raises an OperationFailure' do
        expect do
          result
        end.to raise_exception(Mongo::Error::OperationFailure)
      end
    end

    context 'when a session is provided' do
      let(:session) do
        authorized_client.start_session
      end

      let(:operation) do
        authorized_collection.delete_many({}, session: session)
      end

      let(:failed_operation) do
        authorized_collection.delete_many({ '$._id' => 1 }, session: session)
      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 unacknowledged writes are used with an explicit session' do
      let(:collection_with_unacknowledged_write_concern) do
        authorized_collection.with(write: { w: 0 })
      end

      let(:operation) do
        collection_with_unacknowledged_write_concern.delete_many({ '$._id' => 1 }, session: session)
      end

      it_behaves_like 'an explicit session with an unacknowledged write'
    end

    context 'when unacknowledged writes are used with an implicit session' do
      let(:collection_with_unacknowledged_write_concern) do
        client.with(write: { w: 0 })[TEST_COLL]
      end

      let(:operation) do
        collection_with_unacknowledged_write_concern.delete_many({ '$._id' => 1 })
      end

      it_behaves_like 'an implicit session with an unacknowledged write'
    end

    context 'when a collation is specified' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.delete_many(selector, options)
      end

      before do
        authorized_collection.insert_one(name: 'bang')
        authorized_collection.insert_one(name: 'bang')
      end

      let(:options) do
        { collation: { locale: 'en_US', strength: 2 } }
      end

      context 'when the server selected supports collations' do
        it 'applies the collation' do
          expect(result.written_count).to eq(2)
          expect(authorized_collection.find(name: 'bang').count).to eq(0)
        end

        context 'when unacknowledged writes is used' do
          let(:collection_with_unacknowledged_write_concern) do
            authorized_collection.with(write: { w: 0 })
          end

          let(:result) do
            collection_with_unacknowledged_write_concern.delete_many(selector, options)
          end

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

          context 'when a String key is used' do
            let(:options) do
              { 'collation' => { locale: 'en_US', strength: 2 } }
            end

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

    context 'when a collation is not specified' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.delete_many(selector)
      end

      before do
        authorized_collection.insert_one(name: 'bang')
        authorized_collection.insert_one(name: 'bang')
      end

      it 'does not apply the collation' do
        expect(result.written_count).to eq(0)
        expect(authorized_collection.find(name: 'bang').count).to eq(2)
      end
    end

    context 'when various options passed in' do
      # w: 2 requires a replica set
      require_topology :replica_set

      before do
        collection.insert_many([ { name: 'test1' }, { name: 'test2' }, { name: 'test3' } ])
      end

      let(:selector) do
        { name: 'test1' }
      end

      let(:session) do
        authorized_client.start_session
      end

      let(:events) do
        subscriber.command_started_events('delete')
      end

      let(:collection) do
        authorized_collection.with(write_concern: { w: 1 })
      end

      let!(:command) do
        Utils.get_command_event(authorized_client, 'delete') do |_client|
          collection.delete_many(selector, session: session, write_concern: { w: 2 },
                                           bypass_document_validation: true)
        end.command
      end

      it 'deletes many successfully with correct options sent to server' do
        expect(events.length).to eq(1)
        expect(command[:writeConcern]).not_to be_nil
        expect(command[:writeConcern][:w]).to eq(2)
        expect(command[:bypassDocumentValidation]).to be(true)
      end
    end
  end

  describe '#replace_one' do
    let(:selector) do
      { field: 'test1' }
    end

    context 'when a selector was provided' do
      before do
        authorized_collection.insert_many([ { field: 'test1' }, { field: 'test1' } ])
      end

      let!(:response) do
        authorized_collection.replace_one(selector, { field: 'testing' })
      end

      let(:updated) do
        authorized_collection.find(field: 'testing').first
      end

      it 'updates the first matching document in the collection' do
        expect(response.modified_count).to eq(1)
      end

      it 'updates the documents in the collection' do
        expect(updated[:field]).to eq('testing')
      end
    end

    context 'when upsert is false' do
      let!(:response) do
        authorized_collection.replace_one(selector, { field: 'test1' }, upsert: false)
      end

      let(:updated) do
        authorized_collection.find(field: 'test1').to_a
      end

      it 'reports that no documents were written' do
        expect(response.modified_count).to eq(0)
      end

      it 'does not insert the document' do
        expect(updated).to be_empty
      end
    end

    context 'when upsert is true' do
      let!(:response) do
        authorized_collection.replace_one(selector, { field: 'test1' }, upsert: true)
      end

      let(:updated) do
        authorized_collection.find(field: 'test1').first
      end

      it 'reports that a document was written' do
        expect(response.written_count).to eq(1)
      end

      it 'inserts the document' do
        expect(updated[:field]).to eq('test1')
      end
    end

    context 'when upsert is not specified' do
      let!(:response) do
        authorized_collection.replace_one(selector, { field: 'test1' })
      end

      let(:updated) do
        authorized_collection.find(field: 'test1').to_a
      end

      it 'reports that no documents were written' do
        expect(response.modified_count).to eq(0)
      end

      it 'does not insert the document' do
        expect(updated).to be_empty
      end
    end

    context 'when the replace has an invalid key' do
      context 'when validate_update_replace is true' do
        config_override :validate_update_replace, true

        let(:result) do
          authorized_collection.replace_one(selector, { '$s' => 'test1' })
        end

        it 'raises an InvalidReplacementDocument error' do
          expect do
            result
          end.to raise_exception(Mongo::Error::InvalidReplacementDocument)
        end
      end

      context 'when validate_update_replace is false' do
        config_override :validate_update_replace, false

        let(:result) do
          authorized_collection.replace_one(selector, { '$set' => { 'test1' => 1 } })
        end

        it 'does not raise an error' do
          expect do
            result
          end.not_to raise_exception
        end
      end
    end

    context 'when collection has a validator' do
      around do |spec|
        collection_with_validator.drop
        authorized_client[:validating,
                          validator: { a: { '$exists' => true } }].tap do |c|
          c.create
        end
        spec.run
        collection_with_validator.drop
      end

      before do
        collection_with_validator.insert_one({ a: 1 })
      end

      context 'when the document is valid' do
        let(:result) do
          collection_with_validator.replace_one({ a: 1 }, { a: 5 })
        end

        it 'replaces successfully' do
          expect(result.modified_count).to eq(1)
        end
      end

      context 'when the document is invalid' do
        context 'when bypass_document_validation is not set' do
          let(:result2) do
            collection_with_validator.replace_one({ a: 1 }, { x: 5 })
          end

          it 'raises OperationFailure' do
            expect do
              result2
            end.to raise_exception(Mongo::Error::OperationFailure)
          end
        end

        context 'when bypass_document_validation is true' do
          let(:result3) do
            collection_with_validator.replace_one(
              { a: 1 }, { x: 1 }, bypass_document_validation: true
            )
          end

          it 'replaces successfully' do
            expect(result3.written_count).to eq(1)
          end
        end
      end
    end

    context 'when a collation is specified' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.replace_one(selector, { name: 'doink' }, options)
      end

      before do
        authorized_collection.insert_one(name: 'bang')
      end

      let(:options) do
        { collation: { locale: 'en_US', strength: 2 } }
      end

      context 'when the server selected supports collations' do
        it 'applies the collation' do
          expect(result.written_count).to eq(1)
          expect(authorized_collection.find(name: 'doink').count).to eq(1)
        end

        context 'when unacknowledged writes is used' do
          let(:collection_with_unacknowledged_write_concern) do
            authorized_collection.with(write: { w: 0 })
          end

          let(:result) do
            collection_with_unacknowledged_write_concern.replace_one(selector, { name: 'doink' }, options)
          end

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

          context 'when a String key is used' do
            let(:options) do
              { 'collation' => { locale: 'en_US', strength: 2 } }
            end

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

    context 'when a collation is not specified' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.replace_one(selector, { name: 'doink' })
      end

      before do
        authorized_collection.insert_one(name: 'bang')
      end

      it 'does not apply the collation' do
        expect(result.written_count).to eq(0)
        expect(authorized_collection.find(name: 'bang').count).to eq(1)
      end
    end

    context 'when a session is provided' do
      let(:selector) do
        { name: 'BANG' }
      end

      before do
        authorized_collection.insert_one(name: 'bang')
      end

      let(:session) do
        authorized_client.start_session
      end

      let(:operation) do
        authorized_collection.replace_one(selector, { name: 'doink' }, session: session)
      end

      let(:failed_operation) do
        authorized_collection.replace_one({ '$._id' => 1 }, { name: 'doink' }, session: session)
      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 unacknowledged writes is used with an explicit session' do
      let(:collection_with_unacknowledged_write_concern) do
        authorized_collection.with(write: { w: 0 })
      end

      let(:operation) do
        collection_with_unacknowledged_write_concern.replace_one({ a: 1 }, { x: 5 }, session: session)
      end

      it_behaves_like 'an explicit session with an unacknowledged write'
    end

    context 'when unacknowledged writes is used with an implicit session' do
      let(:collection_with_unacknowledged_write_concern) do
        client.with(write: { w: 0 })[TEST_COLL]
      end

      let(:operation) do
        collection_with_unacknowledged_write_concern.replace_one({ a: 1 }, { x: 5 })
      end

      it_behaves_like 'an implicit session with an unacknowledged write'
    end

    context 'when various options passed in' do
      # w: 2 requires a replica set
      require_topology :replica_set

      before do
        authorized_collection.insert_one({ field: 'test1' })
      end

      let(:session) do
        authorized_client.start_session
      end

      let(:events) do
        subscriber.command_started_events('update')
      end

      let(:collection) do
        authorized_collection.with(write_concern: { w: 3 })
      end

      let(:updated) do
        collection.find(field: 'test4').first
      end

      let!(:command) do
        Utils.get_command_event(authorized_client, 'update') do |_client|
          collection.replace_one(selector, { field: 'test4' },
                                 session: session, return_document: :after, write_concern: { w: 2 },
                                 upsert: true, bypass_document_validation: true)
        end.command
      end

      it 'replaced one successfully with correct options sent to server' do
        expect(updated[:field]).to eq('test4')
        expect(events.length).to eq(1)
        expect(command[:writeConcern]).not_to be_nil
        expect(command[:writeConcern][:w]).to eq(2)
        expect(command[:bypassDocumentValidation]).to be(true)
        expect(command[:updates][0][:upsert]).to be(true)
      end
    end
  end

  describe '#update_many' do
    let(:selector) do
      { field: 'test' }
    end

    context 'when a selector was provided' do
      before do
        authorized_collection.insert_many([ { field: 'test' }, { field: 'test' } ])
      end

      let!(:response) do
        authorized_collection.update_many(selector, '$set' => { field: 'testing' })
      end

      let(:updated) do
        authorized_collection.find(field: 'testing').to_a.last
      end

      it 'returns the number updated' do
        expect(response.modified_count).to eq(2)
      end

      it 'updates the documents in the collection' do
        expect(updated[:field]).to eq('testing')
      end
    end

    context 'when upsert is false' do
      let(:response) do
        authorized_collection.update_many(selector, { '$set' => { field: 'testing' } },
                                          upsert: false)
      end

      let(:updated) do
        authorized_collection.find.to_a
      end

      it 'reports that no documents were updated' do
        expect(response.modified_count).to eq(0)
      end

      it 'updates no documents in the collection' do
        expect(updated).to be_empty
      end
    end

    context 'when upsert is true' do
      let!(:response) do
        authorized_collection.update_many(selector, { '$set' => { field: 'testing' } },
                                          upsert: true)
      end

      let(:updated) do
        authorized_collection.find.sort(_id: 1).to_a.last
      end

      it 'reports that a document was written' do
        expect(response.written_count).to eq(1)
      end

      it 'inserts a document into the collection' do
        expect(updated[:field]).to eq('testing')
      end
    end

    context 'when upsert is not specified' do
      let(:response) do
        authorized_collection.update_many(selector, { '$set' => { field: 'testing' } })
      end

      let(:updated) do
        authorized_collection.find.to_a
      end

      it 'reports that no documents were updated' do
        expect(response.modified_count).to eq(0)
      end

      it 'updates no documents in the collection' do
        expect(updated).to be_empty
      end
    end

    context 'when arrayFilters is provided' do
      let(:selector) do
        { '$or' => [ { _id: 0 }, { _id: 1 } ] }
      end

      context 'when the server supports arrayFilters' do
        before do
          authorized_collection.insert_many([ {
                                              _id: 0, x: [
                                                { y: 1 },
                                                { y: 2 },
                                                { y: 3 }
                                              ]
                                            },
                                              {
                                                _id: 1,
                                                x: [
                                                  { y: 3 },
                                                  { y: 2 },
                                                  { y: 1 }
                                                ]
                                              } ])
        end

        let(:result) do
          authorized_collection.update_many(selector,
                                            { '$set' => { 'x.$[i].y' => 5 } },
                                            options)
        end

        context 'when a Symbol key is used' do
          let(:options) do
            { array_filters: [ { 'i.y' => 3 } ] }
          end

          it 'applies the arrayFilters' do
            expect(result.matched_count).to eq(2)
            expect(result.modified_count).to eq(2)

            docs = authorized_collection.find(selector, sort: { _id: 1 }).to_a
            expect(docs[0]['x']).to eq([ { 'y' => 1 },  { 'y' => 2 }, { 'y' => 5 } ])
            expect(docs[1]['x']).to eq([ { 'y' => 5 },  { 'y' => 2 }, { 'y' => 1 } ])
          end
        end

        context 'when a String key is used' do
          let(:options) do
            { 'array_filters' => [ { 'i.y' => 3 } ] }
          end

          it 'applies the arrayFilters' do
            expect(result.matched_count).to eq(2)
            expect(result.modified_count).to eq(2)

            docs = authorized_collection.find({}, sort: { _id: 1 }).to_a
            expect(docs[0]['x']).to eq([ { 'y' => 1 },  { 'y' => 2 }, { 'y' => 5 } ])
            expect(docs[1]['x']).to eq([ { 'y' => 5 },  { 'y' => 2 }, { 'y' => 1 } ])
          end
        end
      end
    end

    context 'when the updates fail' do
      let(:result) do
        authorized_collection.update_many(selector, { '$s' => { field: 'testing' } })
      end

      it 'raises an OperationFailure' do
        expect do
          result
        end.to raise_exception(Mongo::Error::OperationFailure)
      end
    end

    context 'when collection has a validator' do
      around do |spec|
        authorized_client[:validating,
                          validator: { a: { '$exists' => true } }].tap do |c|
          c.create
        end
        spec.run
        collection_with_validator.drop
      end

      before do
        collection_with_validator.insert_many([ { a: 1 }, { a: 2 } ])
      end

      context 'when the document is valid' do
        let(:result) do
          collection_with_validator.update_many(
            { a: { '$gt' => 0 } }, '$inc' => { a: 1 }
          )
        end

        it 'updates successfully' do
          expect(result.modified_count).to eq(2)
        end
      end

      context 'when the document is invalid' do
        context 'when bypass_document_validation is not set' do
          let(:result2) do
            collection_with_validator.update_many(
              { a: { '$gt' => 0 } }, '$unset' => { a: '' }
            )
          end

          it 'raises OperationFailure' do
            expect do
              result2
            end.to raise_exception(Mongo::Error::OperationFailure)
          end
        end

        context 'when bypass_document_validation is true' do
          let(:result3) do
            collection_with_validator.update_many(
              { a: { '$gt' => 0 } }, { '$unset' => { a: '' } },
              bypass_document_validation: true
            )
          end

          it 'updates successfully' do
            expect(result3.written_count).to eq(2)
          end
        end
      end
    end

    context 'when a collation is specified' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.update_many(selector, { '$set' => { other: 'doink' } }, options)
      end

      before do
        authorized_collection.insert_one(name: 'bang')
        authorized_collection.insert_one(name: 'baNG')
      end

      let(:options) do
        { collation: { locale: 'en_US', strength: 2 } }
      end

      context 'when the server selected supports collations' do
        it 'applies the collation' do
          expect(result.written_count).to eq(2)
          expect(authorized_collection.find(other: 'doink').count).to eq(2)
        end

        context 'when unacknowledged writes is used' do
          let(:collection_with_unacknowledged_write_concern) do
            authorized_collection.with(write: { w: 0 })
          end

          let(:result) do
            collection_with_unacknowledged_write_concern.update_many(selector, { '$set' => { other: 'doink' } },
                                                                     options)
          end

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

          context 'when a String key is used' do
            let(:options) do
              { 'collation' => { locale: 'en_US', strength: 2 } }
            end

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

    context 'when collation is not specified' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.update_many(selector, { '$set' => { other: 'doink' } })
      end

      before do
        authorized_collection.insert_one(name: 'bang')
        authorized_collection.insert_one(name: 'baNG')
      end

      it 'does not apply the collation' do
        expect(result.written_count).to eq(0)
      end
    end

    context 'when a session is provided' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:operation) do
        authorized_collection.update_many(selector, { '$set' => { other: 'doink' } }, session: session)
      end

      before do
        authorized_collection.insert_one(name: 'bang')
        authorized_collection.insert_one(name: 'baNG')
      end

      let(:session) do
        authorized_client.start_session
      end

      let(:failed_operation) do
        authorized_collection.update_many({ '$._id' => 1 }, { '$set' => { other: 'doink' } }, session: session)
      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 unacknowledged writes is used with an explicit session' do
      let(:collection_with_unacknowledged_write_concern) do
        authorized_collection.with(write: { w: 0 })
      end

      let(:operation) do
        collection_with_unacknowledged_write_concern.update_many({ a: 1 }, { '$set' => { x: 1 } }, session: session)
      end

      it_behaves_like 'an explicit session with an unacknowledged write'
    end

    context 'when unacknowledged writes is used with an implicit session' do
      let(:collection_with_unacknowledged_write_concern) do
        client.with(write: { w: 0 })[TEST_COLL]
      end

      let(:operation) do
        collection_with_unacknowledged_write_concern.update_many({ a: 1 }, { '$set' => { x: 1 } })
      end

      it_behaves_like 'an implicit session with an unacknowledged write'
    end

    context 'when various options passed in' do
      # w: 2 requires a replica set
      require_topology :replica_set

      before do
        collection.insert_many([ { field: 'test' }, { field: 'test2' } ], session: session)
      end

      let(:session) do
        authorized_client.start_session
      end

      let(:collection) do
        authorized_collection.with(write_concern: { w: 1 })
      end

      let(:events) do
        subscriber.command_started_events('update')
      end

      let!(:command) do
        Utils.get_command_event(authorized_client, 'update') do |_client|
          collection.update_many(selector, { '$set' => { field: 'testing' } }, session: session,
                                                                               write_concern: { w: 2 }, bypass_document_validation: true, upsert: true)
        end.command
      end

      it 'updates many successfully with correct options sent to server' do
        expect(events.length).to eq(1)
        expect(collection.options[:write_concern]).to eq(w: 1)
        expect(command[:writeConcern][:w]).to eq(2)
        expect(command[:bypassDocumentValidation]).to be(true)
        expect(command[:updates][0][:upsert]).to be(true)
      end
    end
  end

  describe '#update_one' do
    let(:selector) do
      { field: 'test1' }
    end

    context 'when a selector was provided' do
      before do
        authorized_collection.insert_many([ { field: 'test1' }, { field: 'test1' } ])
      end

      let!(:response) do
        authorized_collection.update_one(selector, '$set' => { field: 'testing' })
      end

      let(:updated) do
        authorized_collection.find(field: 'testing').first
      end

      it 'updates the first matching document in the collection' do
        expect(response.modified_count).to eq(1)
      end

      it 'updates the documents in the collection' do
        expect(updated[:field]).to eq('testing')
      end
    end

    context 'when upsert is false' do
      let(:response) do
        authorized_collection.update_one(selector, { '$set' => { field: 'testing' } },
                                         upsert: false)
      end

      let(:updated) do
        authorized_collection.find.to_a
      end

      it 'reports that no documents were updated' do
        expect(response.modified_count).to eq(0)
      end

      it 'updates no documents in the collection' do
        expect(updated).to be_empty
      end
    end

    context 'when upsert is true' do
      let!(:response) do
        authorized_collection.update_one(selector, { '$set' => { field: 'testing' } },
                                         upsert: true)
      end

      let(:updated) do
        authorized_collection.find.first
      end

      it 'reports that a document was written' do
        expect(response.written_count).to eq(1)
      end

      it 'inserts a document into the collection' do
        expect(updated[:field]).to eq('testing')
      end
    end

    context 'when upsert is not specified' do
      let(:response) do
        authorized_collection.update_one(selector, { '$set' => { field: 'testing' } })
      end

      let(:updated) do
        authorized_collection.find.to_a
      end

      it 'reports that no documents were updated' do
        expect(response.modified_count).to eq(0)
      end

      it 'updates no documents in the collection' do
        expect(updated).to be_empty
      end
    end

    context 'when the update fails' do
      let(:result) do
        authorized_collection.update_one(selector, { '$s' => { field: 'testing' } })
      end

      it 'raises an OperationFailure' do
        expect do
          result
        end.to raise_exception(Mongo::Error::OperationFailure)
      end
    end

    context 'when collection has a validator' do
      around do |spec|
        authorized_client[:validating,
                          validator: { a: { '$exists' => true } }].tap do |c|
          c.create
        end
        spec.run
        collection_with_validator.drop
      end

      before do
        collection_with_validator.insert_one({ a: 1 })
      end

      context 'when the document is valid' do
        let(:result) do
          collection_with_validator.update_one(
            { a: { '$gt' => 0 } }, '$inc' => { a: 1 }
          )
        end

        it 'updates successfully' do
          expect(result.modified_count).to eq(1)
        end
      end

      context 'when the document is invalid' do
        context 'when bypass_document_validation is not set' do
          let(:result2) do
            collection_with_validator.update_one(
              { a: { '$gt' => 0 } }, '$unset' => { a: '' }
            )
          end

          it 'raises OperationFailure' do
            expect do
              result2
            end.to raise_exception(Mongo::Error::OperationFailure)
          end
        end

        context 'when bypass_document_validation is true' do
          let(:result3) do
            collection_with_validator.update_one(
              { a: { '$gt' => 0 } }, { '$unset' => { a: '' } },
              bypass_document_validation: true
            )
          end

          it 'updates successfully' do
            expect(result3.written_count).to eq(1)
          end
        end
      end
    end

    context 'when there is a collation specified' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.update_one(selector, { '$set' => { other: 'doink' } }, options)
      end

      before do
        authorized_collection.insert_one(name: 'bang')
      end

      let(:options) do
        { collation: { locale: 'en_US', strength: 2 } }
      end

      context 'when the server selected supports collations' do
        it 'applies the collation' do
          expect(result.written_count).to eq(1)
          expect(authorized_collection.find(other: 'doink').count).to eq(1)
        end

        context 'when unacknowledged writes is used' do
          let(:collection_with_unacknowledged_write_concern) do
            authorized_collection.with(write: { w: 0 })
          end

          let(:result) do
            collection_with_unacknowledged_write_concern.update_one(selector, { '$set' => { other: 'doink' } }, options)
          end

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

          context 'when a String key is used' do
            let(:options) do
              { 'collation' => { locale: 'en_US', strength: 2 } }
            end

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

    context 'when a collation is not specified' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.update_one(selector, { '$set' => { other: 'doink' } })
      end

      before do
        authorized_collection.insert_one(name: 'bang')
      end

      it 'does not apply the collation' do
        expect(result.written_count).to eq(0)
      end
    end

    context 'when arrayFilters is provided' do
      let(:selector) do
        { _id: 0 }
      end

      context 'when the server supports arrayFilters' do
        before do
          authorized_collection.insert_one(_id: 0, x: [ { y: 1 }, { y: 2 }, { y: 3 } ])
        end

        let(:result) do
          authorized_collection.update_one(selector,
                                           { '$set' => { 'x.$[i].y' => 5 } },
                                           options)
        end

        context 'when a Symbol key is used' do
          let(:options) do
            { array_filters: [ { 'i.y' => 3 } ] }
          end

          it 'applies the arrayFilters' do
            expect(result.matched_count).to eq(1)
            expect(result.modified_count).to eq(1)
            expect(authorized_collection.find(selector).first['x'].last['y']).to eq(5)
          end
        end

        context 'when a String key is used' do
          let(:options) do
            { 'array_filters' => [ { 'i.y' => 3 } ] }
          end

          it 'applies the arrayFilters' do
            expect(result.matched_count).to eq(1)
            expect(result.modified_count).to eq(1)
            expect(authorized_collection.find(selector).first['x'].last['y']).to eq(5)
          end
        end
      end
    end

    context 'when the documents are sent with OP_MSG' do
      let(:documents) do
        [ { '_id' => 1, 'name' => '1' * 16_777_191 }, { '_id' => 'y' } ]
      end

      before do
        authorized_collection.insert_many([ { field: 'test1' }, { field: 'test1' } ])
        client[TEST_COLL].update_one({ a: 1 }, { '$set' => { 'name' => '1' * 16_777_149 } })
      end

      let(:update_events) do
        subscriber.started_events.select { |e| e.command_name == 'update' }
      end

      it 'sends the documents in one OP_MSG' do
        expect(update_events.size).to eq(1)
      end
    end

    context 'when a session is provided' do
      before do
        authorized_collection.insert_many([ { field: 'test1' }, { field: 'test1' } ])
      end

      let(:session) do
        authorized_client.start_session
      end

      let(:operation) do
        authorized_collection.update_one({ field: 'test' }, { '$set' => { field: 'testing' } }, session: session)
      end

      let(:failed_operation) do
        authorized_collection.update_one({ '$._id' => 1 }, { '$set' => { field: 'testing' } }, session: session)
      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 unacknowledged writes is used with an explicit session' do
      let(:collection_with_unacknowledged_write_concern) do
        authorized_collection.with(write: { w: 0 })
      end

      let(:operation) do
        collection_with_unacknowledged_write_concern.update_one({ a: 1 }, { '$set' => { x: 1 } }, session: session)
      end

      it_behaves_like 'an explicit session with an unacknowledged write'
    end

    context 'when unacknowledged writes is used with an implicit session' do
      let(:collection_with_unacknowledged_write_concern) do
        client.with(write: { w: 0 })[TEST_COLL]
      end

      let(:operation) do
        collection_with_unacknowledged_write_concern.update_one({ a: 1 }, { '$set' => { x: 1 } })
      end

      it_behaves_like 'an implicit session with an unacknowledged write'
    end

    context 'when various options passed in' do
      # w: 2 requires a replica set
      require_topology :replica_set

      before do
        collection.insert_many([ { field: 'test1' }, { field: 'test2' } ], session: session)
      end

      let(:session) do
        authorized_client.start_session
      end

      let(:collection) do
        authorized_collection.with(write_concern: { w: 1 })
      end

      let(:events) do
        subscriber.command_started_events('update')
      end

      let!(:command) do
        Utils.get_command_event(authorized_client, 'update') do |_client|
          collection.update_one(selector, { '$set' => { field: 'testing' } }, session: session,
                                                                              write_concern: { w: 2 }, bypass_document_validation: true, return_document: :after,
                                                                              upsert: true)
        end.command
      end

      it 'updates one successfully with correct options sent to server' do
        expect(events.length).to eq(1)
        expect(command[:writeConcern]).not_to be_nil
        expect(command[:writeConcern][:w]).to eq(2)
        expect(collection.options[:write_concern]).to eq(w: 1)
        expect(command[:bypassDocumentValidation]).to be(true)
        expect(command[:updates][0][:upsert]).to be(true)
      end
    end
  end

  describe '#find_one_and_delete' do
    before do
      authorized_collection.insert_many([ { field: 'test1' } ])
    end

    let(:selector) do
      { field: 'test1' }
    end

    context 'when a matching document is found' do
      context 'when a session is provided' do
        let(:operation) do
          authorized_collection.find_one_and_delete(selector, session: session)
        end

        let(:failed_operation) do
          authorized_collection.find_one_and_delete({ '$._id' => 1 }, session: session)
        end

        let(:session) do
          authorized_client.start_session
        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 no options are provided' do
        let!(:document) do
          authorized_collection.find_one_and_delete(selector)
        end

        it 'deletes the document from the database' do
          expect(authorized_collection.find.to_a).to be_empty
        end

        it 'returns the document' do
          expect(document['field']).to eq('test1')
        end
      end

      context 'when a projection is provided' do
        let!(:document) do
          authorized_collection.find_one_and_delete(selector, projection: { _id: 1 })
        end

        it 'deletes the document from the database' do
          expect(authorized_collection.find.to_a).to be_empty
        end

        it 'returns the document with limited fields' do
          expect(document['field']).to be_nil
          expect(document['_id']).not_to be_nil
        end
      end

      context 'when a sort is provided' do
        let!(:document) do
          authorized_collection.find_one_and_delete(selector, sort: { field: 1 })
        end

        it 'deletes the document from the database' do
          expect(authorized_collection.find.to_a).to be_empty
        end

        it 'returns the document with limited fields' do
          expect(document['field']).to eq('test1')
        end
      end

      context 'when max_time_ms is provided' do
        it 'includes the max_time_ms value in the command' do
          expect do
            authorized_collection.find_one_and_delete(selector, max_time_ms: 0.1)
          end.to raise_error(Mongo::Error::OperationFailure)
        end
      end
    end

    context 'when no matching document is found' do
      let(:selector) do
        { field: 'test5' }
      end

      let!(:document) do
        authorized_collection.find_one_and_delete(selector)
      end

      it 'returns nil' do
        expect(document).to be_nil
      end
    end

    context 'when the operation fails' do
      let(:result) do
        authorized_collection.find_one_and_delete(selector, max_time_ms: 0.1)
      end

      it 'raises an OperationFailure' do
        expect do
          result
        end.to raise_exception(Mongo::Error::OperationFailure)
      end
    end

    context 'when write_concern is provided' do
      require_topology :single

      it 'uses the write concern' do
        expect do
          authorized_collection.find_one_and_delete(selector,
                                                    write_concern: { w: 2 })
        end.to raise_error(Mongo::Error::OperationFailure)
      end
    end

    context 'when the collection has a write concern' do
      require_topology :single

      let(:collection) do
        authorized_collection.with(write: { w: 2 })
      end

      it 'uses the write concern' do
        expect do
          collection.find_one_and_delete(selector,
                                         write_concern: { w: 2 })
        end.to raise_error(Mongo::Error::OperationFailure)
      end
    end

    context 'when collation is specified' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.find_one_and_delete(selector, options)
      end

      before do
        authorized_collection.insert_one(name: 'bang')
      end

      let(:options) do
        { collation: { locale: 'en_US', strength: 2 } }
      end

      context 'when the server selected supports collations' do
        it 'applies the collation' do
          expect(result['name']).to eq('bang')
          expect(authorized_collection.find(name: 'bang').count).to eq(0)
        end
      end
    end

    context 'when collation is not specified' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.find_one_and_delete(selector)
      end

      before do
        authorized_collection.insert_one(name: 'bang')
      end

      it 'does not apply the collation' do
        expect(result).to be_nil
      end
    end

    context 'when various options passed in' do
      # w: 2 requires a replica set
      require_topology :replica_set

      before do
        authorized_collection.delete_many
        authorized_collection.insert_many([ { name: 'test1' }, { name: 'test2' } ])
      end

      let(:collection) do
        authorized_collection.with(write_concern: { w: 2 })
      end

      let(:session) do
        authorized_client.start_session
      end

      let!(:command) do
        Utils.get_command_event(authorized_client, 'findAndModify') do |_client|
          collection.find_one_and_delete(selector, session: session, write_concern: { w: 2 },
                                                   bypass_document_validation: true, max_time_ms: 300)
        end.command
      end

      let(:events) do
        subscriber.command_started_events('findAndModify')
      end

      it 'finds and deletes successfully with correct options sent to server' do
        expect(events.length).to eq(1)
        expect(command[:writeConcern]).not_to be_nil
        expect(command[:writeConcern][:w]).to eq(2)
        expect(command[:bypassDocumentValidation]).to eq(true)
        expect(command[:maxTimeMS]).to eq(300)
      end
    end
  end

  describe '#find_one_and_update' do
    let(:selector) do
      { field: 'test1' }
    end

    before do
      authorized_collection.insert_many([ { field: 'test1' } ])
    end

    context 'when a matching document is found' do
      context 'when no options are provided' do
        let(:document) do
          authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' } })
        end

        it 'returns the original document' do
          expect(document['field']).to eq('test1')
        end
      end

      context 'when a session is provided' do
        let(:operation) do
          authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' } }, session: session)
        end

        let(:failed_operation) do
          authorized_collection.find_one_and_update({ '$._id' => 1 }, { '$set' => { field: 'testing' } },
                                                    session: session)
        end

        let(:session) do
          authorized_client.start_session
        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 no options are provided' do
        let(:document) do
          authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' } })
        end

        it 'returns the original document' do
          expect(document['field']).to eq('test1')
        end
      end

      context 'when return_document options are provided' do
        context 'when return_document is :after' do
          let(:document) do
            authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' } },
                                                      return_document: :after)
          end

          it 'returns the new document' do
            expect(document['field']).to eq('testing')
          end
        end

        context 'when return_document is :before' do
          let(:document) do
            authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' } },
                                                      return_document: :before)
          end

          it 'returns the original document' do
            expect(document['field']).to eq('test1')
          end
        end
      end

      context 'when a projection is provided' do
        let(:document) do
          authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' } },
                                                    projection: { _id: 1 })
        end

        it 'returns the document with limited fields' do
          expect(document['field']).to be_nil
          expect(document['_id']).not_to be_nil
        end
      end

      context 'when a sort is provided' do
        let(:document) do
          authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' } }, sort: { field: 1 })
        end

        it 'returns the original document' do
          expect(document['field']).to eq('test1')
        end
      end
    end

    context 'when max_time_ms is provided' do
      it 'includes the max_time_ms value in the command' do
        expect do
          authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' } }, max_time_ms: 0.1)
        end.to raise_error(Mongo::Error::OperationFailure)
      end
    end

    context 'when no matching document is found' do
      let(:selector) do
        { field: 'test5' }
      end

      let(:document) do
        authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' } })
      end

      it 'returns nil' do
        expect(document).to be_nil
      end
    end

    context 'when no matching document is found' do
      context 'when no upsert options are provided' do
        let(:selector) do
          { field: 'test5' }
        end

        let(:document) do
          authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' } })
        end

        it 'returns nil' do
          expect(document).to be_nil
        end
      end

      context 'when upsert options are provided' do
        let(:selector) do
          { field: 'test5' }
        end

        let(:document) do
          authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' } }, upsert: true,
                                                                                                  return_document: :after)
        end

        it 'returns the new document' do
          expect(document['field']).to eq('testing')
        end
      end
    end

    context 'when the operation fails' do
      let(:result) do
        authorized_collection.find_one_and_update(selector, { '$set' => { field: 'testing' } }, max_time_ms: 0.1)
      end

      it 'raises an OperationFailure' do
        expect do
          result
        end.to raise_exception(Mongo::Error::OperationFailure)
      end
    end

    context 'when collection has a validator' do
      around do |spec|
        authorized_client[:validating].drop
        authorized_client[:validating,
                          validator: { a: { '$exists' => true } }].tap do |c|
          c.create
        end
        spec.run
        collection_with_validator.drop
      end

      before do
        collection_with_validator.insert_one({ a: 1 })
      end

      context 'when the document is valid' do
        let(:result) do
          collection_with_validator.find_one_and_update(
            { a: 1 }, { '$inc' => { a: 1 } }, return_document: :after
          )
        end

        it 'updates successfully' do
          expect(result['a']).to eq(2)
        end
      end

      context 'when the document is invalid' do
        context 'when bypass_document_validation is not set' do
          let(:result2) do
            collection_with_validator.find_one_and_update(
              { a: 1 }, { '$unset' => { a: '' } }, return_document: :after
            )
          end

          it 'raises OperationFailure' do
            expect do
              result2
            end.to raise_exception(Mongo::Error::OperationFailure)
          end
        end

        context 'when bypass_document_validation is true' do
          let(:result3) do
            collection_with_validator.find_one_and_update(
              { a: 1 }, { '$unset' => { a: '' } },
              bypass_document_validation: true,
              return_document: :after
            )
          end

          it 'updates successfully' do
            expect(result3['a']).to be_nil
          end
        end
      end
    end

    context 'when write_concern is provided' do
      require_topology :single

      it 'uses the write concern' do
        expect do
          authorized_collection.find_one_and_update(selector,
                                                    { '$set' => { field: 'testing' } },
                                                    write_concern: { w: 2 })
        end.to raise_error(Mongo::Error::OperationFailure)
      end
    end

    context 'when the collection has a write concern' do
      require_topology :single

      let(:collection) do
        authorized_collection.with(write: { w: 2 })
      end

      it 'uses the write concern' do
        expect do
          collection.find_one_and_update(selector,
                                         { '$set' => { field: 'testing' } },
                                         write_concern: { w: 2 })
        end.to raise_error(Mongo::Error::OperationFailure)
      end
    end

    context 'when a collation is specified' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.find_one_and_update(selector,
                                                  { '$set' => { other: 'doink' } },
                                                  options)
      end

      before do
        authorized_collection.insert_one(name: 'bang')
      end

      let(:options) do
        { collation: { locale: 'en_US', strength: 2 } }
      end

      context 'when the server selected supports collations' do
        it 'applies the collation' do
          expect(result['name']).to eq('bang')
          expect(authorized_collection.find({ name: 'bang' }, limit: -1).first['other']).to eq('doink')
        end
      end
    end

    context 'when there is no collation specified' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.find_one_and_update(selector, { '$set' => { other: 'doink' } })
      end

      before do
        authorized_collection.insert_one(name: 'bang')
      end

      it 'does not apply the collation' do
        expect(result).to be_nil
      end
    end

    context 'when arrayFilters is provided' do
      let(:selector) do
        { _id: 0 }
      end

      context 'when the server supports arrayFilters' do
        before do
          authorized_collection.insert_one(_id: 0, x: [ { y: 1 }, { y: 2 }, { y: 3 } ])
        end

        let(:result) do
          authorized_collection.find_one_and_update(selector,
                                                    { '$set' => { 'x.$[i].y' => 5 } },
                                                    options)
        end

        context 'when a Symbol key is used' do
          let(:options) do
            { array_filters: [ { 'i.y' => 3 } ] }
          end

          it 'applies the arrayFilters' do
            expect(result['x']).to eq([ { 'y' => 1 }, { 'y' => 2 }, { 'y' => 3 } ])
            expect(authorized_collection.find(selector).first['x'].last['y']).to eq(5)
          end
        end

        context 'when a String key is used' do
          let(:options) do
            { 'array_filters' => [ { 'i.y' => 3 } ] }
          end

          it 'applies the arrayFilters' do
            expect(result['x']).to eq([ { 'y' => 1 }, { 'y' => 2 }, { 'y' => 3 } ])
            expect(authorized_collection.find(selector).first['x'].last['y']).to eq(5)
          end
        end
      end
    end

    context 'when various options passed in' do
      # w: 2 requires a replica set
      require_topology :replica_set

      let(:session) do
        authorized_client.start_session
      end

      let(:events) do
        subscriber.command_started_events('findAndModify')
      end

      let(:collection) do
        authorized_collection.with(write_concern: { w: 2 })
      end

      let(:selector) do
        { field: 'test1' }
      end

      before do
        collection.insert_one({ field: 'test1' }, session: session)
      end

      let!(:command) do
        Utils.get_command_event(authorized_client, 'findAndModify') do |_client|
          collection.find_one_and_update(selector, { '$set' => { field: 'testing' } },
                                         return_document: :after, write_concern: { w: 1 }, upsert: true,
                                         bypass_document_validation: true, max_time_ms: 100, session: session)
        end.command
      end

      it 'find and updates successfully with correct options sent to server' do
        expect(events.length).to eq(1)
        expect(command[:writeConcern]).not_to be_nil
        expect(command[:writeConcern][:w]).to eq(1)
        expect(command[:upsert]).to eq(true)
        expect(command[:bypassDocumentValidation]).to be(true)
        expect(command[:maxTimeMS]).to eq(100)
      end
    end
  end

  describe '#find_one_and_replace' do
    before do
      authorized_collection.insert_many([ { field: 'test1', other: 'sth' } ])
    end

    let(:selector) do
      { field: 'test1' }
    end

    context 'when a matching document is found' do
      context 'when no options are provided' do
        let(:document) do
          authorized_collection.find_one_and_replace(selector, { field: 'testing' })
        end

        it 'returns the original document' do
          expect(document['field']).to eq('test1')
        end
      end

      context 'when a session is provided' do
        let(:operation) do
          authorized_collection.find_one_and_replace(selector, { field: 'testing' }, session: session)
        end

        let(:failed_operation) do
          authorized_collection.find_one_and_replace({ '$._id' => 1 }, { field: 'testing' }, session: session)
        end

        let(:session) do
          authorized_client.start_session
        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 return_document options are provided' do
        context 'when return_document is :after' do
          let(:document) do
            authorized_collection.find_one_and_replace(selector, { field: 'testing' }, return_document: :after)
          end

          it 'returns the new document' do
            expect(document['field']).to eq('testing')
          end
        end

        context 'when return_document is :before' do
          let(:document) do
            authorized_collection.find_one_and_replace(selector, { field: 'testing' }, return_document: :before)
          end

          it 'returns the original document' do
            expect(document['field']).to eq('test1')
          end
        end
      end

      context 'when a projection is provided' do
        let(:document) do
          authorized_collection.find_one_and_replace(selector, { field: 'testing' }, projection: { _id: 1 })
        end

        it 'returns the document with limited fields' do
          expect(document['field']).to be_nil
          expect(document['_id']).not_to be_nil
        end
      end

      context 'when a sort is provided' do
        let(:document) do
          authorized_collection.find_one_and_replace(selector, { field: 'testing' }, sort: { field: 1 })
        end

        it 'returns the original document' do
          expect(document['field']).to eq('test1')
        end
      end
    end

    context 'when no matching document is found' do
      context 'when no upsert options are provided' do
        let(:selector) do
          { field: 'test5' }
        end

        let(:document) do
          authorized_collection.find_one_and_replace(selector, { field: 'testing' })
        end

        it 'returns nil' do
          expect(document).to be_nil
        end
      end

      context 'when upsert options are provided' do
        let(:selector) do
          { field: 'test5' }
        end

        let(:document) do
          authorized_collection.find_one_and_replace(selector, { field: 'testing' }, upsert: true,
                                                                                     return_document: :after)
        end

        it 'returns the new document' do
          expect(document['field']).to eq('testing')
        end
      end
    end

    context 'when max_time_ms is provided' do
      it 'includes the max_time_ms value in the command' do
        expect do
          authorized_collection.find_one_and_replace(selector, { field: 'testing' }, max_time_ms: 0.1)
        end.to raise_error(Mongo::Error::OperationFailure)
      end
    end

    context 'when the operation fails' do
      let(:result) do
        authorized_collection.find_one_and_replace(selector, { field: 'testing' }, max_time_ms: 0.1)
      end

      it 'raises an OperationFailure' do
        expect do
          result
        end.to raise_exception(Mongo::Error::OperationFailure)
      end
    end

    context 'when collection has a validator' do
      around do |spec|
        authorized_client[:validating].drop
        authorized_client[:validating,
                          validator: { a: { '$exists' => true } }].tap do |c|
          c.create
        end
        spec.run
        collection_with_validator.drop
      end

      before do
        collection_with_validator.insert_one({ a: 1 })
      end

      context 'when the document is valid' do
        let(:result) do
          collection_with_validator.find_one_and_replace(
            { a: 1 }, { a: 5 }, return_document: :after
          )
        end

        it 'replaces successfully when document is valid' do
          expect(result[:a]).to eq(5)
        end
      end

      context 'when the document is invalid' do
        context 'when bypass_document_validation is not set' do
          let(:result2) do
            collection_with_validator.find_one_and_replace(
              { a: 1 }, { x: 5 }, return_document: :after
            )
          end

          it 'raises OperationFailure' do
            expect do
              result2
            end.to raise_exception(Mongo::Error::OperationFailure)
          end
        end

        context 'when bypass_document_validation is true' do
          let(:result3) do
            collection_with_validator.find_one_and_replace(
              { a: 1 }, { x: 1 }, bypass_document_validation: true,
                                  return_document: :after
            )
          end

          it 'replaces successfully' do
            expect(result3[:x]).to eq(1)
            expect(result3[:a]).to be_nil
          end
        end
      end
    end

    context 'when write_concern is provided' do
      require_topology :single

      it 'uses the write concern' do
        expect do
          authorized_collection.find_one_and_replace(selector,
                                                     { field: 'testing' },
                                                     write_concern: { w: 2 })
        end.to raise_error(Mongo::Error::OperationFailure)
      end
    end

    context 'when the collection has a write concern' do
      require_topology :single

      let(:collection) do
        authorized_collection.with(write: { w: 2 })
      end

      it 'uses the write concern' do
        expect do
          collection.find_one_and_replace(selector,
                                          { field: 'testing' },
                                          write_concern: { w: 2 })
        end.to raise_error(Mongo::Error::OperationFailure)
      end
    end

    context 'when collation is provided' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.find_one_and_replace(selector,
                                                   { name: 'doink' },
                                                   options)
      end

      before do
        authorized_collection.insert_one(name: 'bang')
      end

      let(:options) do
        { collation: { locale: 'en_US', strength: 2 } }
      end

      context 'when the server selected supports collations' do
        it 'applies the collation' do
          expect(result['name']).to eq('bang')
          expect(authorized_collection.find(name: 'doink').count).to eq(1)
        end
      end
    end

    context 'when collation is not specified' do
      let(:selector) do
        { name: 'BANG' }
      end

      let(:result) do
        authorized_collection.find_one_and_replace(selector, { name: 'doink' })
      end

      before do
        authorized_collection.insert_one(name: 'bang')
      end

      it 'does not apply the collation' do
        expect(result).to be_nil
      end
    end

    context 'when various options passed in' do
      before do
        authorized_collection.insert_one({ field: 'test1' })
      end

      let(:session) do
        authorized_client.start_session
      end

      let(:events) do
        subscriber.command_started_events('findAndModify')
      end

      let(:collection) do
        authorized_collection.with(write_concern: { w: 2 })
      end

      let!(:command) do
        Utils.get_command_event(authorized_client, 'findAndModify') do |_client|
          collection.find_one_and_replace(selector, { '$set' => { field: 'test5' } },
                                          return_document: :after, write_concern: { w: 1 }, session: session,
                                          upsert: true, bypass_document_validation: false, max_time_ms: 200)
        end.command
      end

      it 'find and replaces successfully with correct options sent to server' do
        expect(events.length).to eq(1)
        expect(command[:writeConcern]).not_to be_nil
        expect(command[:writeConcern][:w]).to eq(1)
        expect(command[:upsert]).to be(true)
        expect(command[:bypassDocumentValidation]).to be false
        expect(command[:maxTimeMS]).to eq(200)
      end
    end
  end

  context 'when unacknowledged writes is used on find_one_and_update' do
    let(:selector) do
      { name: 'BANG' }
    end

    let(:collection_with_unacknowledged_write_concern) do
      authorized_collection.with(write: { w: 0 })
    end

    let(:result) do
      collection_with_unacknowledged_write_concern.find_one_and_update(selector,
                                                                       { '$set' => { field: 'testing' } },
                                                                       write_concern: { w: 0 })
    end

    it 'does not raise an exception' do
      expect(result).to be_nil
    end
  end

  context 'when creating collection with view_on and pipeline' do
    before do
      authorized_client['my_view'].drop
      authorized_collection.insert_one({ bar: 'here!' })
      authorized_client['my_view',
                        view_on: authorized_collection.name,
                        pipeline: [ { '$project': { baz: '$bar' } } ]
      ].create
    end

    it 'the view has a document' do
      expect(authorized_client['my_view'].find.to_a.length).to eq(1)
    end

    it 'applies the pipeline' do
      expect(authorized_client['my_view'].find.first).to have_key('baz')
      expect(authorized_client['my_view'].find.first['baz']).to eq('here!')
    end
  end
end
