require_relative 'test_helper'

# Kubernetes client entity tests
class KubeclientTest < MiniTest::Test
  def test_json
    our_object = Kubeclient::Resource.new
    our_object.foo = 'bar'
    our_object.nested = {}
    our_object.nested.again = {}
    our_object.nested.again.again = {}
    our_object.nested.again.again.name = 'aaron'

    expected = {
      'foo' => 'bar',
      'nested' => { 'again' => { 'again' => { 'name' => 'aaron' } } }
    }

    assert_equal(expected, JSON.parse(JSON.dump(our_object.to_h)))
  end

  def test_pass_uri
    # URI::Generic#hostname= was added in ruby 1.9.3 and will automatically
    # wrap an ipv6 address in []
    uri = URI::HTTP.build(port: 8080)
    uri.hostname = 'localhost'
    client = Kubeclient::Client.new(uri)
    rest_client = client.rest_client
    assert_equal('http://localhost:8080/api/v1', rest_client.url.to_s)
  end

  def test_no_path_in_uri
    client = Kubeclient::Client.new('http://localhost:8080', 'v1')
    rest_client = client.rest_client
    assert_equal('http://localhost:8080/api/v1', rest_client.url.to_s)
  end

  def test_no_version_passed
    client = Kubeclient::Client.new('http://localhost:8080')
    rest_client = client.rest_client
    assert_equal('http://localhost:8080/api/v1', rest_client.url.to_s)
  end

  def test_pass_proxy
    uri = URI::HTTP.build(host: 'localhost', port: 8080)
    proxy_uri = URI::HTTP.build(host: 'myproxyhost', port: 8888)
    stub_core_api_list

    client = Kubeclient::Client.new(uri, http_proxy_uri: proxy_uri)
    rest_client = client.rest_client
    assert_equal(proxy_uri.to_s, rest_client.options[:proxy])

    watch_client = client.watch_pods
    assert_equal(watch_client.send(:build_client_options)[:proxy][:proxy_address], proxy_uri.host)
    assert_equal(watch_client.send(:build_client_options)[:proxy][:proxy_port], proxy_uri.port)
  end

  def test_exception
    stub_core_api_list
    stub_request(:post, %r{/services})
      .to_return(body: open_test_file('namespace_exception.json'), status: 409)

    service = Kubeclient::Resource.new
    service.metadata = {}
    service.metadata.name = 'redisslave'
    service.metadata.namespace = 'default'
    # service.port = 80
    # service.container_port = 6379
    # service.protocol = 'TCP'

    client = Kubeclient::Client.new('http://localhost:8080/api/')

    exception = assert_raises(Kubeclient::HttpError) do
      service = client.create_service(service)
    end

    assert_instance_of(Kubeclient::HttpError, exception)
    assert_equal("converting  to : type names don't match (Pod, Namespace)",
                 exception.message)

    assert_includes(exception.to_s, ' for POST http://localhost:8080/api')
    assert_equal(409, exception.error_code)
  end

  def test_deprecated_exception
    error_message = 'certificate verify failed'

    stub_request(:get, 'http://localhost:8080/api')
      .to_raise(OpenSSL::SSL::SSLError.new(error_message))

    client = Kubeclient::Client.new('http://localhost:8080/api/')

    exception = assert_raises(KubeException) { client.api }
    assert_equal(error_message, exception.message)
  end

  def test_api
    stub_request(:get, 'http://localhost:8080/api')
      .to_return(status: 200, body: open_test_file('versions_list.json'))

    response = client.api
    assert_includes(response, 'versions')
  end

  def test_api_ssl_failure
    error_message = 'certificate verify failed'

    stub_request(:get, 'http://localhost:8080/api')
      .to_raise(OpenSSL::SSL::SSLError.new(error_message))

    client = Kubeclient::Client.new('http://localhost:8080/api/')

    exception = assert_raises(Kubeclient::HttpError) { client.api }
    assert_equal(error_message, exception.message)
  end

  def test_api_timeout
    stub_request(:get, 'http://localhost:8080/api').to_timeout

    client = Kubeclient::Client.new('http://localhost:8080/api/')

    exception = assert_raises(Kubeclient::HttpError) { client.api }
    assert_match(/(timed out|timeout)/i, exception.message)
  end

  def test_api_valid
    stub_request(:get, 'http://localhost:8080/api')
      .to_return(status: 200, body: open_test_file('versions_list.json'))

    args = ['http://localhost:8080/api/']

    [nil, 'v1beta3', 'v1'].each do |version|
      client = Kubeclient::Client.new(*(version ? args + [version] : args))
      assert client.api_valid?
    end
  end

  def test_api_valid_with_invalid_version
    stub_request(:get, 'http://localhost:8080/api')
      .to_return(status: 200, body: open_test_file('versions_list.json'))

    client = Kubeclient::Client.new('http://localhost:8080/api/', 'foobar1')
    refute client.api_valid?
  end

  def test_api_valid_with_unreported_versions
    stub_request(:get, 'http://localhost:8080/api')
      .to_return(status: 200, body: '{}')

    client = Kubeclient::Client.new('http://localhost:8080/api/')
    refute client.api_valid?
  end

  def test_api_valid_with_invalid_json
    stub_request(:get, 'http://localhost:8080/api')
      .to_return(status: 200, body: '[]')

    client = Kubeclient::Client.new('http://localhost:8080/api/')
    refute client.api_valid?
  end

  def test_api_valid_with_bad_endpoint
    stub_request(:get, 'http://localhost:8080/api')
      .to_return(status: [404, 'Resource Not Found'])

    client = Kubeclient::Client.new('http://localhost:8080/api/')
    assert_raises(Kubeclient::HttpError) { client.api_valid? }
  end

  def test_api_valid_with_non_json
    stub_request(:get, 'http://localhost:8080/api')
      .to_return(status: 200, body: '<html></html>')

    client = Kubeclient::Client.new('http://localhost:8080/api/')
    assert_raises(JSON::ParserError) { client.api_valid? }
  end

  def test_nonjson_exception
    stub_core_api_list
    stub_request(:get, %r{/servic})
      .to_return(body: open_test_file('service_illegal_json_404.json'), status: 404)

    exception = assert_raises(Kubeclient::ResourceNotFoundError) do
      client.get_services
    end

    assert(exception.message.include?('Not Found'))
    assert_equal(404, exception.error_code)
  end

  def test_nonjson_exception_raw
    stub_core_api_list
    stub_request(:get, %r{/servic})
      .to_return(body: open_test_file('service_illegal_json_404.json'), status: 404)

    exception = assert_raises(Kubeclient::ResourceNotFoundError) do
      client.get_services(as: :raw)
    end

    assert(exception.message.include?('Not Found'))
    assert_equal(404, exception.error_code)
  end

  def test_entity_list
    stub_core_api_list
    stub_get_services

    services = client.get_services

    refute_empty(services)
    assert_instance_of(Kubeclient::Common::EntityList, services)
    # Stripping of 'List' in collection.kind RecursiveOpenStruct mode only is historic.
    assert_equal('Service', services.kind)
    assert_equal(2, services.size)
    assert_instance_of(Kubeclient::Resource, services[0])
    assert_instance_of(Kubeclient::Resource, services[1])

    assert_requested(:get, 'http://localhost:8080/api/v1/services', times: 1)
  end

  def test_entity_list_raw
    stub_core_api_list
    stub_get_services

    response = client.get_services(as: :raw)

    refute_empty(response)
    assert_equal(open_test_file('entity_list.json').read, response)

    assert_requested(:get, 'http://localhost:8080/api/v1/services', times: 1)
  end

  def test_entity_list_parsed
    stub_core_api_list
    stub_get_services

    response = client.get_services(as: :parsed)
    assert_equal Hash, response.class
    assert_equal 'ServiceList', response['kind']
    assert_equal %w[metadata spec status], response['items'].first.keys
  end

  def test_entity_list_parsed_symbolized
    stub_core_api_list
    stub_get_services

    response = client.get_services(as: :parsed_symbolized)
    assert_equal Hash, response.class
    assert_equal 'ServiceList', response[:kind]
    assert_equal %i[metadata spec status], response[:items].first.keys
  end

  def test_entity_list_unknown
    stub_core_api_list
    stub_get_services

    e = assert_raises(ArgumentError) { client.get_services(as: :whoops) }
    assert_equal 'Unsupported format :whoops', e.message
  end

  def test_entity_list_raw_failure
    stub_core_api_list
    stub_request(:get, %r{/services})
      .to_return(body: open_test_file('entity_list.json'), status: 500)

    exception = assert_raises(Kubeclient::HttpError) { client.get_services(as: :raw) }
    assert_equal('500 Internal Server Error', exception.message)
    assert_equal(500, exception.error_code)
  end

  def test_entities_with_label_selector
    selector = 'component=apiserver'

    stub_core_api_list
    stub_get_services

    services = client.get_services(label_selector: selector)

    assert_instance_of(Kubeclient::Common::EntityList, services)
    assert_requested(
      :get,
      "http://localhost:8080/api/v1/services?labelSelector=#{selector}",
      times: 1
    )
  end

  def test_entities_with_field_selector
    selector = 'involvedObject.name=redis-master'

    stub_core_api_list
    stub_get_services

    services = client.get_services(field_selector: selector)

    assert_instance_of(Kubeclient::Common::EntityList, services)
    assert_requested(
      :get,
      "http://localhost:8080/api/v1/services?fieldSelector=#{selector}",
      times: 1
    )
  end

  def test_empty_list
    stub_core_api_list
    stub_request(:get, %r{/pods})
      .to_return(body: open_test_file('empty_pod_list.json'), status: 200)

    pods = client.get_pods
    assert_instance_of(Kubeclient::Common::EntityList, pods)
    assert_equal(0, pods.size)
  end

  def test_get_all
    stub_core_api_list

    stub_request(:get, %r{/bindings})
      .to_return(body: open_test_file('bindings_list.json'), status: 404)

    stub_request(:get, %r{/configmaps})
      .to_return(body: open_test_file('config_map_list.json'), status: 200)

    stub_request(:get, %r{/podtemplates})
      .to_return(body: open_test_file('pod_template_list.json'), status: 200)

    stub_request(:get, %r{/services})
      .to_return(body: open_test_file('service_list.json'), status: 200)

    stub_request(:get, %r{/pods})
      .to_return(body: open_test_file('pod_list.json'), status: 200)

    stub_request(:get, %r{/nodes})
      .to_return(body: open_test_file('node_list.json'), status: 200)

    stub_request(:get, %r{/replicationcontrollers})
      .to_return(body: open_test_file('replication_controller_list.json'), status: 200)

    stub_request(:get, %r{/events})
      .to_return(body: open_test_file('event_list.json'), status: 200)

    stub_request(:get, %r{/endpoints})
      .to_return(body: open_test_file('endpoint_list.json'), status: 200)

    stub_request(:get, %r{/namespaces})
      .to_return(body: open_test_file('namespace_list.json'), status: 200)

    stub_request(:get, %r{/secrets})
      .to_return(body: open_test_file('secret_list.json'), status: 200)

    stub_request(:get, %r{/resourcequotas})
      .to_return(body: open_test_file('resource_quota_list.json'), status: 200)

    stub_request(:get, %r{/limitranges})
      .to_return(body: open_test_file('limit_range_list.json'), status: 200)

    stub_request(:get, %r{/persistentvolumes})
      .to_return(body: open_test_file('persistent_volume_list.json'), status: 200)

    stub_request(:get, %r{/persistentvolumeclaims})
      .to_return(body: open_test_file('persistent_volume_claim_list.json'), status: 200)

    stub_request(:get, %r{/componentstatuses})
      .to_return(body: open_test_file('component_status_list.json'), status: 200)

    stub_request(:get, %r{/serviceaccounts})
      .to_return(body: open_test_file('service_account_list.json'), status: 200)

    result = client.all_entities
    assert_equal(16, result.keys.size)
    assert_instance_of(Kubeclient::Common::EntityList, result['node'])
    assert_instance_of(Kubeclient::Common::EntityList, result['service'])
    assert_instance_of(Kubeclient::Common::EntityList, result['replication_controller'])
    assert_instance_of(Kubeclient::Common::EntityList, result['pod'])
    assert_instance_of(Kubeclient::Common::EntityList, result['event'])
    assert_instance_of(Kubeclient::Common::EntityList, result['namespace'])
    assert_instance_of(Kubeclient::Common::EntityList, result['secret'])
    assert_instance_of(Kubeclient::Resource, result['service'][0])
    assert_instance_of(Kubeclient::Resource, result['node'][0])
    assert_instance_of(Kubeclient::Resource, result['event'][0])
    assert_instance_of(Kubeclient::Resource, result['endpoint'][0])
    assert_instance_of(Kubeclient::Resource, result['namespace'][0])
    assert_instance_of(Kubeclient::Resource, result['secret'][0])
    assert_instance_of(Kubeclient::Resource, result['resource_quota'][0])
    assert_instance_of(Kubeclient::Resource, result['limit_range'][0])
    assert_instance_of(Kubeclient::Resource, result['persistent_volume'][0])
    assert_instance_of(Kubeclient::Resource, result['persistent_volume_claim'][0])
    assert_instance_of(Kubeclient::Resource, result['component_status'][0])
    assert_instance_of(Kubeclient::Resource, result['service_account'][0])
  end

  def test_get_all_raw
    stub_core_api_list

    stub_request(:get, %r{/bindings})
      .to_return(body: open_test_file('bindings_list.json'), status: 404)

    stub_request(:get, %r{/configmaps})
      .to_return(body: open_test_file('config_map_list.json'), status: 200)

    stub_request(:get, %r{/podtemplates})
      .to_return(body: open_test_file('pod_template_list.json'), status: 200)

    stub_request(:get, %r{/services})
      .to_return(body: open_test_file('service_list.json'), status: 200)

    stub_request(:get, %r{/pods})
      .to_return(body: open_test_file('pod_list.json'), status: 200)

    stub_request(:get, %r{/nodes})
      .to_return(body: open_test_file('node_list.json'), status: 200)

    stub_request(:get, %r{/replicationcontrollers})
      .to_return(body: open_test_file('replication_controller_list.json'), status: 200)

    stub_request(:get, %r{/events})
      .to_return(body: open_test_file('event_list.json'), status: 200)

    stub_request(:get, %r{/endpoints})
      .to_return(body: open_test_file('endpoint_list.json'), status: 200)

    stub_request(:get, %r{/namespaces})
      .to_return(body: open_test_file('namespace_list.json'), status: 200)

    stub_request(:get, %r{/secrets})
      .to_return(body: open_test_file('secret_list.json'), status: 200)

    stub_request(:get, %r{/resourcequotas})
      .to_return(body: open_test_file('resource_quota_list.json'), status: 200)

    stub_request(:get, %r{/limitranges})
      .to_return(body: open_test_file('limit_range_list.json'), status: 200)

    stub_request(:get, %r{/persistentvolumes})
      .to_return(body: open_test_file('persistent_volume_list.json'), status: 200)

    stub_request(:get, %r{/persistentvolumeclaims})
      .to_return(body: open_test_file('persistent_volume_claim_list.json'), status: 200)

    stub_request(:get, %r{/componentstatuses})
      .to_return(body: open_test_file('component_status_list.json'), status: 200)

    stub_request(:get, %r{/serviceaccounts})
      .to_return(body: open_test_file('service_account_list.json'), status: 200)

    result = client.all_entities(as: :raw)
    assert_equal(16, result.keys.size)

    %w[
      component_status config_map endpoint event limit_range namespace node
      persistent_volume persistent_volume_claim pod replication_controller
      resource_quota secret service service_account
    ].each do |entity|
      assert_equal(open_test_file("#{entity}_list.json").read, result[entity])
    end
  end

  def test_api_bearer_token_with_params_success
    stub_request(:get, 'http://localhost:8080/api/v1/pods?labelSelector=name=redis-master')
      .with(headers: { Authorization: 'Bearer valid_token' })
      .to_return(body: open_test_file('pod_list.json'), status: 200)
    stub_request(:get, %r{/api/v1$})
      .with(headers: { Authorization: 'Bearer valid_token' })
      .to_return(body: open_test_file('core_api_resource_list.json'), status: 200)

    client = Kubeclient::Client.new(
      'http://localhost:8080/api/',
      auth_options: { bearer_token: 'valid_token' }
    )

    pods = client.get_pods(label_selector: 'name=redis-master')

    assert_equal('Pod', pods.kind)
    assert_equal(1, pods.size)
  end

  def test_api_bearer_token_success
    stub_core_api_list
    stub_request(:get, 'http://localhost:8080/api/v1/pods')
      .with(headers: { Authorization: 'Bearer valid_token' })
      .to_return(
        body: open_test_file('pod_list.json'), status: 200
      )

    client = Kubeclient::Client.new(
      'http://localhost:8080/api/',
      auth_options: { bearer_token: 'valid_token' }
    )

    pods = client.get_pods

    assert_equal('Pod', pods.kind)
    assert_equal(1, pods.size)
  end

  def test_api_bearer_token_failure
    error_message =
      '"/api/v1" is forbidden because ' \
      'system:anonymous cannot list on pods in'
    response = OpenStruct.new(code: 401, message: error_message)

    stub_request(:get, 'http://localhost:8080/api/v1')
      .with(headers: { Authorization: 'Bearer invalid_token' })
      .to_raise(Kubeclient::HttpError.new(403, error_message, response))

    client = Kubeclient::Client.new(
      'http://localhost:8080/api/',
      auth_options: { bearer_token: 'invalid_token' }
    )

    exception = assert_raises(Kubeclient::HttpError) { client.get_pods }
    assert_equal(403, exception.error_code)
    assert_equal(error_message, exception.message)
    assert_equal(response, exception.response)
  end

  def test_api_bearer_token_failure_raw
    error_message =
      '"/api/v1" is forbidden because ' \
      'system:anonymous cannot list on pods in'
    response = OpenStruct.new(code: 401, message: error_message)

    stub_request(:get, 'http://localhost:8080/api/v1')
      .with(headers: { Authorization: 'Bearer invalid_token' })
      .to_raise(Kubeclient::HttpError.new(403, error_message, response))

    client = Kubeclient::Client.new(
      'http://localhost:8080/api/',
      auth_options: { bearer_token: 'invalid_token' }
    )

    exception = assert_raises(Kubeclient::HttpError) { client.get_pods(as: :raw) }
    assert_equal(403, exception.error_code)
    assert_equal(error_message, exception.message)
    assert_equal(response, exception.response)
  end

  def test_api_basic_auth_success
    stub_request(:get, 'http://localhost:8080/api/v1')
      .with(basic_auth: %w[username password])
      .to_return(body: open_test_file('core_api_resource_list.json'), status: 200)
    stub_request(:get, 'http://localhost:8080/api/v1/pods')
      .with(basic_auth: %w[username password])
      .to_return(body: open_test_file('pod_list.json'), status: 200)

    client = Kubeclient::Client.new(
      'http://localhost:8080/api/',
      auth_options: { username: 'username', password: 'password' }
    )

    pods = client.get_pods

    assert_equal('Pod', pods.kind)
    assert_equal(1, pods.size)
    assert_requested(
      :get,
      'http://localhost:8080/api/v1/pods',
      times: 1
    )
  end

  def test_api_basic_auth_back_comp_success
    stub_request(:get, 'http://localhost:8080/api/v1')
      .with(basic_auth: %w[username password])
      .to_return(body: open_test_file('core_api_resource_list.json'), status: 200)
    stub_request(:get, 'http://localhost:8080/api/v1/pods')
      .with(basic_auth: %w[username password])
      .to_return(body: open_test_file('pod_list.json'), status: 200)

    client = Kubeclient::Client.new(
      'http://localhost:8080/api/',
      auth_options: { user: 'username', password: 'password' }
    )

    pods = client.get_pods

    assert_equal('Pod', pods.kind)
    assert_equal(1, pods.size)
    assert_requested(:get, 'http://localhost:8080/api/v1/pods', times: 1)
  end

  def test_api_basic_auth_failure
    error_message = 'HTTP status code 401, 401 Unauthorized'
    response = OpenStruct.new(code: 401, message: '401 Unauthorized')

    stub_request(:get, 'http://localhost:8080/api/v1')
      .with(basic_auth: %w[username password])
      .to_raise(Kubeclient::HttpError.new(401, error_message, response))

    client = Kubeclient::Client.new(
      'http://localhost:8080/api/',
      auth_options: { username: 'username', password: 'password' }
    )

    exception = assert_raises(Kubeclient::HttpError) { client.get_pods }
    assert_equal(401, exception.error_code)
    assert_equal(error_message, exception.message)
    assert_equal(response, exception.response)
    assert_requested(:get, 'http://localhost:8080/api/v1', times: 1)
  end

  def test_api_basic_auth_failure_raw
    error_message = 'HTTP status code 401, 401 Unauthorized'
    response = OpenStruct.new(code: 401, message: '401 Unauthorized')

    stub_request(:get, 'http://localhost:8080/api/v1')
      .with(basic_auth: %w[username password])
      .to_raise(Kubeclient::HttpError.new(401, error_message, response))

    client = Kubeclient::Client.new(
      'http://localhost:8080/api/',
      auth_options: { username: 'username', password: 'password' }
    )

    exception = assert_raises(Kubeclient::HttpError) { client.get_pods(as: :raw) }
    assert_equal(401, exception.error_code)
    assert_equal(error_message, exception.message)
    assert_equal(response, exception.response)

    assert_requested(:get, 'http://localhost:8080/api/v1', times: 1)
  end

  def test_init_username_no_password
    expected_msg = 'Basic auth requires both username & password'
    exception = assert_raises(ArgumentError) do
      Kubeclient::Client.new(
        'http://localhost:8080',
        auth_options: { username: 'username' }
      )
    end
    assert_equal(expected_msg, exception.message)
  end

  def test_init_user_no_password
    expected_msg = 'Basic auth requires both username & password'
    exception = assert_raises(ArgumentError) do
      Kubeclient::Client.new(
        'http://localhost:8080',
        auth_options: { user: 'username' }
      )
    end
    assert_equal(expected_msg, exception.message)
  end

  def test_init_username_and_bearer_token
    expected_msg = 'Invalid auth options: specify only one of username/password,' \
      ' bearer_token or bearer_token_file'
    exception = assert_raises(ArgumentError) do
      Kubeclient::Client.new(
        'http://localhost:8080',
        auth_options: { username: 'username', bearer_token: 'token' }
      )
    end
    assert_equal(expected_msg, exception.message)
  end

  def test_init_username_and_bearer_token_file
    expected_msg = 'Invalid auth options: specify only one of username/password,' \
      ' bearer_token or bearer_token_file'
    exception = assert_raises(ArgumentError) do
      Kubeclient::Client.new(
        'http://localhost:8080',
        auth_options: { username: 'username', bearer_token_file: 'token-file' }
      )
    end
    assert_equal(expected_msg, exception.message)
  end

  def test_bearer_token_and_bearer_token_file
    expected_msg =
      'Invalid auth options: specify only one of username/password,' \
      ' bearer_token or bearer_token_file'
    exception = assert_raises(ArgumentError) do
      Kubeclient::Client.new(
        'http://localhost:8080',
        auth_options: { bearer_token: 'token', bearer_token_file: 'token-file' }
      )
    end
    assert_equal(expected_msg, exception.message)
  end

  def test_bearer_token_file_not_exist
    expected_msg = 'Token file token-file does not exist'
    exception = assert_raises(ArgumentError) do
      Kubeclient::Client.new(
        'http://localhost:8080',
        auth_options: { bearer_token_file: 'token-file' }
      )
    end
    assert_equal(expected_msg, exception.message)
  end

  def test_api_bearer_token_file_success
    stub_core_api_list
    stub_request(:get, 'http://localhost:8080/api/v1/pods')
      .with(headers: { Authorization: 'Bearer valid_token' })
      .to_return(body: open_test_file('pod_list.json'), status: 200)

    file = File.join(File.dirname(__FILE__), 'valid_token_file')
    client = Kubeclient::Client.new(
      'http://localhost:8080/api/',
      auth_options: { bearer_token_file: file }
    )

    pods = client.get_pods

    assert_equal('Pod', pods.kind)
    assert_equal(1, pods.size)
  end

  def test_proxy_url
    stub_core_api_list

    client = Kubeclient::Client.new('http://host:8080', 'v1')
    assert_equal(
      'http://host:8080/api/v1/namespaces/ns/services/srvname:srvportname/proxy',
      client.proxy_url('service', 'srvname', 'srvportname', 'ns')
    )

    assert_equal(
      'http://host:8080/api/v1/namespaces/ns/services/srvname:srvportname/proxy',
      client.proxy_url('services', 'srvname', 'srvportname', 'ns')
    )

    assert_equal(
      'http://host:8080/api/v1/namespaces/ns/pods/srvname:srvportname/proxy',
      client.proxy_url('pod', 'srvname', 'srvportname', 'ns')
    )

    assert_equal(
      'http://host:8080/api/v1/namespaces/ns/pods/srvname:srvportname/proxy',
      client.proxy_url('pods', 'srvname', 'srvportname', 'ns')
    )

    # Check no namespace provided
    assert_equal(
      'http://host:8080/api/v1/nodes/srvname:srvportname/proxy',
      client.proxy_url('nodes', 'srvname', 'srvportname')
    )

    assert_equal(
      'http://host:8080/api/v1/nodes/srvname:srvportname/proxy',
      client.proxy_url('node', 'srvname', 'srvportname')
    )

    # Check integer port
    assert_equal(
      'http://host:8080/api/v1/nodes/srvname:5001/proxy',
      client.proxy_url('nodes', 'srvname', 5001)
    )

    assert_equal(
      'http://host:8080/api/v1/nodes/srvname:5001/proxy',
      client.proxy_url('node', 'srvname', 5001)
    )
  end

  def test_attr_readers
    client = Kubeclient::Client.new(
      'http://localhost:8080/api/',
      ssl_options: { client_key: 'secret' },
      auth_options: { bearer_token: 'token' }
    )
    assert_equal('/api', client.api_endpoint.path)
    assert_equal('secret', client.ssl_options[:client_key])
    assert_equal('token', client.auth_options[:bearer_token])
    assert_equal('Bearer token', client.headers[:Authorization])
  end

  def test_nil_items
    # handle https://github.com/kubernetes/kubernetes/issues/13096
    stub_core_api_list
    stub_request(:get, %r{/persistentvolumeclaims})
      .to_return(body: open_test_file('persistent_volume_claims_nil_items.json'), status: 200)

    client.get_persistent_volume_claims
  end

  # Timeouts

  def test_timeouts_defaults
    client = Kubeclient::Client.new(
      'http://localhost:8080/api/'
    )
    rest_client = client.rest_client
    assert_default_open_timeout(rest_client.open_timeout)
    assert_equal(60, rest_client.read_timeout)
  end

  def test_timeouts_open
    client = Kubeclient::Client.new(
      'http://localhost:8080/api/',
      timeouts: { open: 10 }
    )
    rest_client = client.rest_client
    assert_equal(10, rest_client.open_timeout)
    assert_equal(60, rest_client.read_timeout)
  end

  def test_timeouts_read
    client = Kubeclient::Client.new(
      'http://localhost:8080/api/',
      timeouts: { read: 300 }
    )
    rest_client = client.rest_client
    assert_default_open_timeout(rest_client.open_timeout)
    assert_equal(300, rest_client.read_timeout)
  end

  def test_timeouts_both
    client = Kubeclient::Client.new(
      'http://localhost:8080/api/',
      timeouts: { open: 10, read: 300 }
    )
    rest_client = client.rest_client
    assert_equal(10, rest_client.open_timeout)
    assert_equal(300, rest_client.read_timeout)
  end

  def test_timeouts_infinite
    client = Kubeclient::Client.new(
      'http://localhost:8080/api/',
      timeouts: { open: nil, read: nil }
    )
    rest_client = client.rest_client
    assert_nil(rest_client.open_timeout)
    assert_nil(rest_client.read_timeout)
  end

  def assert_default_open_timeout(actual)
    if RUBY_VERSION >= '2.3'
      assert_equal(60, actual)
    else
      assert_nil(actual)
    end
  end

  private

  def stub_get_services
    stub_request(:get, %r{/services})
      .to_return(body: open_test_file('entity_list.json'), status: 200)
  end

  def client
    @client ||= Kubeclient::Client.new('http://localhost:8080/api/', 'v1')
  end

  # dup method creates a shallow copy which is not good in this case
  # since rename_keys changes the input hash
  # hence need to create a deep_copy
  def deep_copy(hash)
    Marshal.load(Marshal.dump(hash))
  end
end
