# frozen_string_literal: true

require 'spec_helper'

describe Mongo::Operation::Update do
  require_no_required_api_version

  let(:context) { Mongo::Operation::Context.new }

  let(:document) do
    { q: { foo: 1 },
      u: { :$set => { bar: 1 } },
      multi: true,
      upsert: false }
  end

  let(:spec) do
    { updates: [ document ],
      db_name: SpecConfig.instance.test_db,
      coll_name: TEST_COLL,
      write_concern: Mongo::WriteConcern.get(w: 1),
      ordered: true }
  end

  let(:update) do
    described_class.new(spec)
  end

  describe '#initialize' do
    context 'spec' do
      it 'sets the spec' do
        expect(update.spec).to eq(spec)
      end
    end
  end

  describe '#==' do
    context 'spec' do
      context 'when two ops have the same specs' do
        let(:other) { described_class.new(spec) }

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

      context 'when two ops have different specs' do
        let(:other_doc) do
          { q: { foo: 1 },
            u: { :$set => { bar: 1 } },
            multi: true,
            upsert: true }
        end
        let(:other_spec) do
          { update: other_doc,
            db_name: SpecConfig.instance.test_db,
            coll_name: TEST_COLL,
            write_concern: Mongo::WriteConcern.get(w: 1),
            ordered: true }
        end

        let(:other) { described_class.new(other_spec) }

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

  describe '#execute' do
    before do
      authorized_collection.drop
      authorized_collection.insert_many([
                                          { name: 'test', field: 'test', other: 'test' },
                                          { name: 'testing', field: 'test', other: 'test' }
                                        ])
    end

    after do
      authorized_collection.delete_many
    end

    context 'when updating a single document' do
      let(:update) do
        described_class.new({
                              updates: [ document ],
                              db_name: SpecConfig.instance.test_db,
                              coll_name: TEST_COLL,
                              write_concern: Mongo::WriteConcern.get(w: 1)
                            })
      end

      context 'when the update succeeds' do
        let(:document) do
          { 'q' => { name: 'test' }, 'u' => { '$set' => { field: 'blah' } } }
        end

        let(:result) do
          update.execute(authorized_primary, context: context)
        end

        it 'updates the document' do
          expect(result.written_count).to eq(1)
        end

        it 'reports the modified count' do
          expect(result.modified_count).to eq(1)
        end

        it 'reports the matched count' do
          expect(result.matched_count).to eq(1)
        end

        it 'reports the upserted id as nil' do
          expect(result.upserted_id).to eq(nil)
        end
      end

      context 'when the update fails' do
        let(:document) do
          { 'q' => { name: 'test' }, 'u' => { '$st' => { field: 'blah' } } }
        end

        it 'raises an exception' do
          expect do
            update.execute(authorized_primary, context: context)
          end.to raise_error(Mongo::Error::OperationFailure)
        end
      end
    end

    context 'when updating multiple documents' do
      let(:update) do
        described_class.new({
                              updates: [ document ],
                              db_name: SpecConfig.instance.test_db,
                              coll_name: TEST_COLL,
                              write_concern: Mongo::WriteConcern.get(w: 1)
                            })
      end

      context 'when the updates succeed' do
        let(:document) do
          { 'q' => { field: 'test' }, 'u' => { '$set' => { other: 'blah' } }, 'multi' => true }
        end

        let(:result) do
          update.execute(authorized_primary, context: context)
        end

        it 'updates the documents' do
          expect(result.written_count).to eq(2)
        end

        it 'reports the modified count' do
          expect(result.modified_count).to eq(2)
        end

        it 'reports the matched count' do
          expect(result.matched_count).to eq(2)
        end

        it 'reports the upserted id as nil' do
          expect(result.upserted_id).to eq(nil)
        end
      end

      context 'when an update fails' do
        let(:document) do
          { 'q' => { name: 'test' }, 'u' => { '$st' => { field: 'blah' } }, 'multi' => true }
        end

        it 'raises an exception' do
          expect do
            update.execute(authorized_primary, context: context)
          end.to raise_error(Mongo::Error::OperationFailure)
        end
      end

      context 'when a document exceeds max bson size' do
        let(:document) do
          { 'q' => { name: 't' * 17_000_000 }, 'u' => { '$set' => { field: 'blah' } } }
        end

        it 'raises an error' do
          expect do
            update.execute(authorized_primary, context: context)
          end.to raise_error(Mongo::Error::MaxBSONSize)
        end
      end

      context 'when upsert is true' do
        let(:document) do
          { 'q' => { field: 'non-existent' }, 'u' => { '$set' => { other: 'blah' } }, 'upsert' => true }
        end

        let(:result) do
          update.execute(authorized_primary, context: context)
        end

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

        it 'reports the modified count' do
          expect(result.modified_count).to eq(0)
        end

        it 'reports the matched count' do
          expect(result.matched_count).to eq(0)
        end

        it 'returns the upserted id' do
          expect(result.upserted_id).to be_a(BSON::ObjectId)
        end
      end
    end
  end
end
