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

require 'spec_helper'

describe Riak::Client::BeefcakeProtobuffsBackend do
  before(:all) { expect(described_class).to be_configured }
  let(:client) { Riak::Client.new }
  let(:node) { client.nodes.first }
  let(:backend) { Riak::Client::BeefcakeProtobuffsBackend.new(client, node) }
  let(:protocol){ double 'protocol' }
  before(:each) do
    allow(backend).to receive(:get_server_version).and_return("2.0.0")
    allow(backend).to receive(:protocol).and_yield(protocol)
  end

  context "secondary index" do
    before :each do
      @socket = double(:socket).as_null_object
      allow(TCPSocket).to receive(:new).and_return(@socket)
    end

    it 'raises an appropriate error when 2i is not available' do
      expect(protocol).to receive(:write)
      expect(protocol).to receive(:expect).
        and_raise(
                  Riak::ProtobuffsErrorResponse.
                  new(Riak::Client::BeefcakeProtobuffsBackend::
                      RpbErrorResp.
                      new(errmsg: '{error,{indexes_not_supported,riak_kv_bitcask_backend}}',
                          errcode: 0)
                      )
                  )

      expect{ backend.get_index 'bucket', 'words', 'asdf' }.to raise_error /Secondary indexes aren't supported/
      # '
    end

    context 'when streaming' do
      it "streams when a block is given" do
        expect(protocol).to receive(:write) do |msg, req|
          expect(msg).to eq(:IndexReq)
          expect(req[:stream]).to eq(true)
        end
        expect(protocol).to receive(:expect).
          and_return(Riak::Client::BeefcakeProtobuffsBackend::RpbIndexResp.new keys: %w{'asdf'}, done: true)

        blk = proc{:asdf}

        backend.get_index('bucket', 'words', 'asdf'..'hjkl', &blk)
      end

      it "sends batches of results to the block" do
        expect(protocol).to receive(:write)

        response_sets = [%w{asdf asdg asdh}, %w{gggg gggh gggi}]
        response_messages = response_sets.map do |s|
          Riak::Client::BeefcakeProtobuffsBackend::RpbIndexResp.new keys: s
        end
        response_messages.last.done = true

        expect(protocol).to receive(:expect).and_return(*response_messages)

        block_body = double 'block'
        expect(block_body).to receive(:check).with(response_sets.first).once
        expect(block_body).to receive(:check).with(response_sets.last).once

        blk = proc {|m| block_body.check m }

        backend.get_index 'bucket', 'words', 'asdf'..'hjkl', &blk
      end
    end

    it "returns a full batch of results when not streaming" do
      expect(protocol).to receive(:write) do |msg, req|
        expect(msg).to eq(:IndexReq)
        expect(req[:stream]).not_to be
      end

      response_message = Riak::Client::BeefcakeProtobuffsBackend::
        RpbIndexResp.new(
                         keys: %w{asdf asdg asdh}
                         )
      expect(protocol).to receive(:expect).
        and_return(response_message)

      results = backend.get_index 'bucket', 'words', 'asdf'..'hjkl'
      expect(results).to eq(%w{asdf asdg asdh})
    end

    it "returns no results when no keys or terms are returned" do
      expect(protocol).to receive(:write) do |msg, req|
        expect(msg).to eq(:IndexReq)
        expect(req[:stream]).not_to be
      end

      response_message = Riak::Client::BeefcakeProtobuffsBackend::
        RpbIndexResp.new()

      expect(protocol).to receive(:expect).and_return(response_message)

      results = nil
      fetch = proc do
        results = backend.get_index 'bucket', 'words', 'asdf'
      end

      expect(fetch).not_to raise_error
      expect(results).to eq([])
    end
  end

  context "#mapred" do
    let(:mapred) { Riak::MapReduce.new(client).add('test').map("function(){}").map("function(){}") }

    it "returns empty sets for previous phases that don't return anything" do
      expect(protocol).to receive(:write)

      message = double(:message, :phase => 1, :response => [{}].to_json)
      allow(message).to receive(:done).and_return(false, true)

      expect(protocol).to receive(:expect).
        twice.
        and_return(message)

      expect(backend.mapred(mapred)).to eq([{}])
    end
  end

  context "preventing stale writes" do
    before do
      allow(backend).to receive(:decode_response).and_return(nil)
      allow(backend).to receive(:get_server_version).and_return("1.0.3")
    end

    let(:robject) do
      Riak::RObject.new(client['stale'], 'prevent').tap do |obj|
        obj.prevent_stale_writes = true
        obj.raw_data = "stale"
        obj.content_type = "text/plain"
      end
    end

    it "sets the if_none_match field when the object is new" do
      expect(protocol).to receive(:write) do |msg, req|
        expect(msg).to eq(:PutReq)
        expect(req.if_none_match).to be_truthy
      end
      expect(protocol).to receive(:expect).
        and_return(:empty)

      backend.store_object(robject)
    end

    it "sets the if_not_modified field when the object has a vclock" do
      robject.vclock = Base64.encode64("foo")
      expect(protocol).to receive(:write) do |msg, req|
        expect(msg).to eq(:PutReq)
        expect(req.if_not_modified).to be_truthy
      end
      expect(protocol).to receive(:expect).
        and_return(:empty)
      backend.store_object(robject)
    end
  end
end
