# 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 'socket'
require 'spec_helper'

require 'riak/client/beefcake/messages'
require 'riak/client/beefcake/protocol'

describe 'Protocol Buffers', test_client: true, integration: true, slow: true do
  if RUBY_VERSION >= '2.0.0'
    describe 'timeouts' do
      it 'raises error on connect timeout' do
        # unroutable TEST-NET (https://tools.ietf.org/html/rfc5737)
        config = {}
        config[:host] = '192.0.2.0'
        config[:pb_port] = 65535

        config[:connect_timeout] = 0.0001
        client = Riak::Client.new(config)

        expect do
          client.ping
        end.to raise_error RuntimeError, /timed out/
      end

      it 'raises error on read timeout' do
        ok_to_continue = false
        quitting = false
        port = 0

        server = nil
        thr = Thread.new do
          server = TCPServer.new port
          port = server.addr[1]
          ok_to_continue = true
          loop do
            begin
              Thread.start(server.accept) do |s|
                loop do
                  p = Riak::Client::BeefcakeProtobuffsBackend::Protocol.new s
                  begin
                    msgname, _body = p.receive
                  rescue IOError
                    break if quitting
                    raise
                  end
                  case msgname
                  when :PingReq
                    sleep 0.5
                    p.write :PingResp
                  else
                    $stderr.puts("unknown msgname: #{msgname}")
                  end
                end
              end
            rescue IOError
              break if quitting
              raise
            end
          end
        end

        loop do
          break if ok_to_continue
          sleep 0.1
        end
        ok_to_continue = false

        config = {}
        config[:pb_port] = port
        config[:client_id] = port
        config[:read_timeout] = 0.0001
        client = Riak::Client.new(config)

        max_ping_attempts = 16
        ping_count = 0
        loop do
          begin
            client.ping
            ping_count += 1
            break if ping_count > max_ping_attempts
          rescue RuntimeError => e
            break if e.message =~ /timed out/
          end
          sleep 0.5
        end

        quitting = true
        server.close
        thr.join

        expect(ping_count).to be < max_ping_attempts
      end

      it 'raises error on write timeout' do
        ok_to_continue = false
        quitting = false
        port = 0

        server = nil
        thr = Thread.new do
          server = TCPServer.new port
          port = server.addr[1]
          ok_to_continue = true
          loop do
            begin
              Thread.start(server.accept) do |s|
                loop do
                  p = Riak::Client::BeefcakeProtobuffsBackend::Protocol.new s
                  begin
                    msgname, _body = p.receive
                  rescue IOError
                    break if quitting
                    raise
                  end
                  case msgname
                  when :PingReq
                    p.write :PingResp
                  when :GetServerInfoReq
                    r = Riak::Client::BeefcakeProtobuffsBackend::RpbGetServerInfoResp.new
                    r.node = 'dev1@127.0.0.1'.force_encoding('BINARY')
                    r.server_version = '2.1.4'.force_encoding('BINARY')
                    p.write :GetServerInfoResp, r
                  when :PutReq
                    r = Riak::Client::BeefcakeProtobuffsBackend::RpbPutResp.new
                    p.write :PutResp, r
                  else
                    $stderr.puts("unknown msgname: #{msgname}")
                  end
                end
              end
            rescue IOError
              break if quitting
              raise
            end
          end
        end

        loop do
          break if ok_to_continue
          sleep 0.1
        end
        ok_to_continue = false

        config = {}
        config[:pb_port] = port
        config[:client_id] = port
        config[:write_timeout] = 0.0001
        client = Riak::Client.new(config)

        bucket = client.bucket('timeouts')

        max_store_attempts = 16
        store_count = 0
        loop do
          begin
            obj = bucket.new "obj-#{store_count}"
            # write enough data to grow beyond socket buffer capacity
            obj.data = SecureRandom.urlsafe_base64(10_000_000)
            obj.content_type = 'text/plain'
            obj.store
            store_count += 1
            break if store_count > max_store_attempts
          rescue RuntimeError => e
            break if e.message =~ /timed out/
          end
          sleep 0.5
        end

        quitting = true
        server.close
        thr.join

        expect(store_count).to be < max_store_attempts
      end
    end
  else
    skip 'not supported in this version of Ruby'
  end
end
