require 'spec_helper'
RSpec.describe HTTParty::Request do
before do
@request = HTTParty::Request.new(Net::HTTP::Get, 'http://api.foo.com/v1', format: :xml)
end
describe "::NON_RAILS_QUERY_STRING_NORMALIZER" do
let(:normalizer) { HTTParty::Request::NON_RAILS_QUERY_STRING_NORMALIZER }
it "doesn't modify strings" do
query_string = normalizer["foo=bar&foo=baz"]
expect(CGI.unescape(query_string)).to eq("foo=bar&foo=baz")
end
context "when the query is an array" do
it "doesn't include brackets" do
query_string = normalizer[{page: 1, foo: %w(bar baz)}]
expect(CGI.unescape(query_string)).to eq("foo=bar&foo=baz&page=1")
end
it "URI encodes array values" do
query_string = normalizer[{people: ["Otis Redding", "Bob Marley", "Tim & Jon"], page: 1, xyzzy: 3}]
expect(query_string).to eq("page=1&people=Otis%20Redding&people=Bob%20Marley&people=Tim%20%26%20Jon&xyzzy=3")
end
end
context "when the query is a hash" do
it "correctly handles nil values" do
query_string = normalizer[{page: 1, per_page: nil}]
expect(query_string).to eq("page=1&per_page")
end
end
end
describe "::JSON_API_QUERY_STRING_NORMALIZER" do
let(:normalizer) { HTTParty::Request::JSON_API_QUERY_STRING_NORMALIZER }
it "doesn't modify strings" do
query_string = normalizer["foo=bar&foo=baz"]
expect(CGI.unescape(query_string)).to eq("foo=bar&foo=baz")
end
context "when the query is an array" do
it "doesn't include brackets" do
query_string = normalizer[{page: 1, foo: %w(bar baz)}]
expect(CGI.unescape(query_string)).to eq("foo=bar,baz&page=1")
end
it "URI encodes array values" do
query_string = normalizer[{people: ["Otis Redding", "Bob Marley", "Tim & Jon"], page: 1, xyzzy: 3}]
expect(query_string).to eq("page=1&people=Otis%20Redding,Bob%20Marley,Tim%20%26%20Jon&xyzzy=3")
end
end
context "when the query is a hash" do
it "correctly handles nil values" do
query_string = normalizer[{page: 1, per_page: nil}]
expect(query_string).to eq('page=1&per_page')
end
end
end
describe "initialization" do
it "sets parser to HTTParty::Parser" do
request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com')
expect(request.parser).to eq(HTTParty::Parser)
end
it "sets parser to the optional parser" do
my_parser = lambda {}
request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com', parser: my_parser)
expect(request.parser).to eq(my_parser)
end
it "sets connection_adapter to HTTParty::ConnectionAdapter" do
request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com')
expect(request.connection_adapter).to eq(HTTParty::ConnectionAdapter)
end
it "sets connection_adapter to the optional connection_adapter" do
my_adapter = lambda {}
request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com', connection_adapter: my_adapter)
expect(request.connection_adapter).to eq(my_adapter)
end
context "when using a query string" do
context "and it has an empty array" do
it "sets correct query string" do
request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com', query: { fake_array: [] })
expect(request.uri).to eq(URI.parse("http://google.com/?fake_array[]="))
end
end
context "when sending an array with only one element" do
it "sets correct query" do
request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com', query: { fake_array: [1] })
expect(request.uri).to eq(URI.parse("http://google.com/?fake_array[]=1"))
end
end
end
context "when basic authentication credentials provided in uri" do
context "when basic auth options wasn't set explicitly" do
it "sets basic auth from uri" do
request = HTTParty::Request.new(Net::HTTP::Get, 'http://user1:pass1@example.com')
expect(request.options[:basic_auth]).to eq({username: 'user1', password: 'pass1'})
end
end
context "when basic auth options was set explicitly" do
it "uses basic auth from url anyway" do
basic_auth = {username: 'user2', password: 'pass2'}
request = HTTParty::Request.new(Net::HTTP::Get, 'http://user1:pass1@example.com', basic_auth: basic_auth)
expect(request.options[:basic_auth]).to eq({username: 'user1', password: 'pass1'})
end
end
end
end
describe "#format" do
context "request yet to be made" do
it "returns format option" do
request = HTTParty::Request.new 'get', '/', format: :xml
expect(request.format).to eq(:xml)
end
it "returns nil format" do
request = HTTParty::Request.new 'get', '/'
expect(request.format).to be_nil
end
end
context "request has been made" do
it "returns format option" do
request = HTTParty::Request.new 'get', '/', format: :xml
request.last_response = double
expect(request.format).to eq(:xml)
end
it "returns the content-type from the last response when the option is not set" do
request = HTTParty::Request.new 'get', '/'
response = double
expect(response).to receive(:[]).with('content-type').and_return('text/json')
request.last_response = response
expect(request.format).to eq(:json)
end
end
end
context "options" do
it "should use basic auth when configured" do
@request.options[:basic_auth] = {username: 'foobar', password: 'secret'}
@request.send(:setup_raw_request)
expect(@request.instance_variable_get(:@raw_request)['authorization']).not_to be_nil
end
context 'digest_auth' do
before do
response_sequence = [
{
status: ['401', 'Unauthorized' ], headers: {
www_authenticate: 'Digest realm="Log Viewer", qop="auth", nonce="2CA0EC6B0E126C4800E56BA0C0003D3C", opaque="5ccc069c403ebaf9f0171e9517f40e41", stale=false',
set_cookie: 'custom-cookie=1234567'
}
},
{ status: ['200', 'OK'] }
]
stub_request(:get, 'http://api.foo.com/v1').to_return(response_sequence)
end
it 'should not send credentials more than once' do
response_sequence = [
{
status: ['401', 'Unauthorized' ], headers: {
www_authenticate: 'Digest realm="Log Viewer", qop="auth", nonce="2CA0EC6B0E126C4800E56BA0C0003D3C", opaque="5ccc069c403ebaf9f0171e9517f40e41", stale=false',
set_cookie: 'custom-cookie=1234567'
}
},
{
status: ['401', 'Unauthorized' ], headers: {
www_authenticate: 'Digest realm="Log Viewer", qop="auth", nonce="2CA0EC6B0E126C4800E56BA0C0003D3C", opaque="5ccc069c403ebaf9f0171e9517f40e41", stale=false',
set_cookie: 'custom-cookie=1234567'
}
},
{ status: ['404', 'Not found'] }
]
stub_request(:get, 'http://api.foo.com/v1').to_return(response_sequence)
@request.options[:digest_auth] = {username: 'foobar', password: 'secret'}
response = @request.perform { |v| }
expect(response.code).to eq(401)
raw_request = @request.instance_variable_get(:@raw_request)
expect(raw_request['Authorization']).not_to be_nil
end
it 'should not be used when configured and the response is 200' do
stub_request(:get, 'http://api.foo.com/v1').to_return(status: 200)
@request.options[:digest_auth] = {username: 'foobar', password: 'secret'}
response = @request.perform { |v| }
expect(response.code).to eq(200)
raw_request = @request.instance_variable_get(:@raw_request)
expect(raw_request['Authorization']).to be_nil
end
it "should be used when configured and the response is 401" do
@request.options[:digest_auth] = {username: 'foobar', password: 'secret'}
response = @request.perform { |v| }
expect(response.code).to eq(200)
raw_request = @request.instance_variable_get(:@raw_request)
expect(raw_request['Authorization']).not_to be_nil
end
it 'should maintain cookies returned from a 401 response' do
@request.options[:digest_auth] = {username: 'foobar', password: 'secret'}
response = @request.perform {|v|}
expect(response.code).to eq(200)
raw_request = @request.instance_variable_get(:@raw_request)
expect(raw_request.get_fields('cookie')).to eql ["custom-cookie=1234567"]
end
it 'should merge cookies from request and a 401 response' do
@request.options[:digest_auth] = {username: 'foobar', password: 'secret'}
@request.options[:headers] = {'cookie' => 'request-cookie=test'}
response = @request.perform {|v|}
expect(response.code).to eq(200)
raw_request = @request.instance_variable_get(:@raw_request)
expect(raw_request.get_fields('cookie')).to eql ['request-cookie=test', 'custom-cookie=1234567']
end
end
it 'should use body_stream when configured' do
stream = StringIO.new('foo')
request = HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', body_stream: stream)
request.send(:setup_raw_request)
expect(request.instance_variable_get(:@raw_request).body_stream).to eq(stream)
end
it 'should normalize base uri when specified as request option' do
stub_request(:get, 'http://foo.com/resource').to_return(body: 'Bar')
response = HTTParty.get('/resource', {
base_uri: 'foo.com'
})
expect(response.code).to eq(200)
end
end
describe "#uri" do
context "redirects" do
it "returns correct path when the server sets the location header to a filename" do
@request.last_uri = URI.parse("http://example.com/foo/bar")
@request.path = URI.parse("bar?foo=bar")
@request.redirect = true
expect(@request.uri).to eq(URI.parse("http://example.com/foo/bar?foo=bar"))
end
context "location header is an absolute path" do
it "returns correct path when location has leading slash" do
@request.last_uri = URI.parse("http://example.com/foo/bar")
@request.path = URI.parse("/bar?foo=bar")
@request.redirect = true
expect(@request.uri).to eq(URI.parse("http://example.com/bar?foo=bar"))
end
it "returns the correct path when location has no leading slash" do
@request.last_uri = URI.parse("http://example.com")
@request.path = URI.parse("bar/")
@request.redirect = true
expect(@request.uri).to eq(URI.parse("http://example.com/bar/"))
end
end
it "returns correct path when the server sets the location header to a full uri" do
@request.last_uri = URI.parse("http://example.com/foo/bar")
@request.path = URI.parse("http://example.com/bar?foo=bar")
@request.redirect = true
expect(@request.uri).to eq(URI.parse("http://example.com/bar?foo=bar"))
end
it "returns correct path when the server sets the location header to a network-path reference" do
@request.last_uri = URI.parse("https://example.com")
@request.path = URI.parse("//www.example.com")
@request.redirect = true
expect(@request.uri).to eq(URI.parse("https://www.example.com"))
end
end
context "query strings" do
it "does not add an empty query string when default_params are blank" do
@request.options[:default_params] = {}
expect(@request.uri.query).to be_nil
end
it "respects the query string normalization proc" do
empty_proc = lambda {|qs| "I"}
@request.options[:query_string_normalizer] = empty_proc
@request.options[:query] = {foo: :bar}
expect(CGI.unescape(@request.uri.query)).to eq("I")
end
it "does not append an ampersand when queries are embedded in paths" do
@request.path = "/path?a=1"
@request.options[:query] = {}
expect(@request.uri.query).to eq("a=1")
end
it "does not duplicate query string parameters when uri is called twice" do
@request.options[:query] = {foo: :bar}
@request.uri
expect(@request.uri.query).to eq("foo=bar")
end
context "when representing an array" do
it "returns a Rails style query string" do
@request.options[:query] = {foo: %w(bar baz)}
expect(CGI.unescape(@request.uri.query)).to eq("foo[]=bar&foo[]=baz")
end
end
end
end
describe "#setup_raw_request" do
context "when query_string_normalizer is set" do
it "sets the body to the return value of the proc" do
@request.options[:query_string_normalizer] = HTTParty::Request::NON_RAILS_QUERY_STRING_NORMALIZER
@request.options[:body] = {page: 1, foo: %w(bar baz)}
@request.send(:setup_raw_request)
body = @request.instance_variable_get(:@raw_request).body
expect(CGI.unescape(body)).to eq("foo=bar&foo=baz&page=1")
end
end
context "when mulipart" do
subject(:headers) do
@request.send(:setup_raw_request)
headers = @request.instance_variable_get(:@raw_request).each_header.to_a
Hash[*headers.flatten] # Ruby 2.0 doesn't have Array#to_h
end
context "when body contains file" do
it "sets header Content-Type: multipart/form-data; boundary=" do
@request.options[:body] = {file: File.open(File::NULL, 'r')}
expect(headers['content-type']).to match(%r{^multipart/form-data; boundary=---})
end
context "and header Content-Type is provided" do
it "overwrites the header to: multipart/form-data; boundary=" do
@request.options[:body] = {file: File.open(File::NULL, 'r')}
@request.options[:headers] = {'Content-Type' => 'application/x-www-form-urlencoded'}
expect(headers['content-type']).to match(%r{^multipart/form-data; boundary=---})
end
end
end
context 'when mulipart option is provided' do
it "sets header Content-Type: multipart/form-data; boundary=" do
@request.options[:body] = { text: 'something' }
@request.options[:multipart] = true
expect(headers['content-type']).to match(%r{^multipart/form-data; boundary=---})
end
end
end
end
describe 'http' do
it "should get a connection from the connection_adapter" do
http = Net::HTTP.new('google.com')
adapter = double('adapter')
request = HTTParty::Request.new(Net::HTTP::Get, 'https://api.foo.com/v1:443', connection_adapter: adapter)
expect(adapter).to receive(:call).with(request.uri, request.options).and_return(http)
expect(request.send(:http)).to be http
end
end
describe '#format_from_mimetype' do
it 'should handle text/xml' do
["text/xml", "text/xml; charset=iso8859-1"].each do |ct|
expect(@request.send(:format_from_mimetype, ct)).to eq(:xml)
end
end
it 'should handle application/xml' do
["application/xml", "application/xml; charset=iso8859-1"].each do |ct|
expect(@request.send(:format_from_mimetype, ct)).to eq(:xml)
end
end
it 'should handle text/json' do
["text/json", "text/json; charset=iso8859-1"].each do |ct|
expect(@request.send(:format_from_mimetype, ct)).to eq(:json)
end
end
it 'should handle application/vnd.api+json' do
["application/vnd.api+json", "application/vnd.api+json; charset=iso8859-1"].each do |ct|
expect(@request.send(:format_from_mimetype, ct)).to eq(:json)
end
end
it 'should handle application/hal+json' do
["application/hal+json", "application/hal+json; charset=iso8859-1"].each do |ct|
expect(@request.send(:format_from_mimetype, ct)).to eq(:json)
end
end
it 'should handle application/json' do
["application/json", "application/json; charset=iso8859-1"].each do |ct|
expect(@request.send(:format_from_mimetype, ct)).to eq(:json)
end
end
it 'should handle text/csv' do
["text/csv", "text/csv; charset=iso8859-1"].each do |ct|
expect(@request.send(:format_from_mimetype, ct)).to eq(:csv)
end
end
it 'should handle application/csv' do
["application/csv", "application/csv; charset=iso8859-1"].each do |ct|
expect(@request.send(:format_from_mimetype, ct)).to eq(:csv)
end
end
it 'should handle text/comma-separated-values' do
["text/comma-separated-values", "text/comma-separated-values; charset=iso8859-1"].each do |ct|
expect(@request.send(:format_from_mimetype, ct)).to eq(:csv)
end
end
it 'should handle text/javascript' do
["text/javascript", "text/javascript; charset=iso8859-1"].each do |ct|
expect(@request.send(:format_from_mimetype, ct)).to eq(:plain)
end
end
it 'should handle application/javascript' do
["application/javascript", "application/javascript; charset=iso8859-1"].each do |ct|
expect(@request.send(:format_from_mimetype, ct)).to eq(:plain)
end
end
it "returns nil for an unrecognized mimetype" do
expect(@request.send(:format_from_mimetype, "application/atom+xml")).to be_nil
end
it "returns nil when using a default parser" do
@request.options[:parser] = lambda {}
expect(@request.send(:format_from_mimetype, "text/json")).to be_nil
end
end
describe 'parsing responses' do
it 'should handle xml automatically' do
xml = '1234Foo Bar!'
@request.options[:format] = :xml
expect(@request.send(:parse_response, xml)).to eq({'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}})
end
it 'should handle utf-8 bom in xml' do
xml = "\xEF\xBB\xBF1234Foo Bar!"
@request.options[:format] = :xml
expect(@request.send(:parse_response, xml)).to eq({'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}})
end
it 'should handle csv automatically' do
csv = ['"id","Name"', '"1234","Foo Bar!"'].join("\n")
@request.options[:format] = :csv
expect(@request.send(:parse_response, csv)).to eq([%w(id Name), ["1234", "Foo Bar!"]])
end
it 'should handle json automatically' do
json = '{"books": {"book": {"name": "Foo Bar!", "id": "1234"}}}'
@request.options[:format] = :json
expect(@request.send(:parse_response, json)).to eq({'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}})
end
it 'should handle utf-8 bom in json' do
json = "\xEF\xBB\xBF{\"books\": {\"book\": {\"name\": \"Foo Bar!\", \"id\": \"1234\"}}}"
@request.options[:format] = :json
expect(@request.send(:parse_response, json)).to eq({'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}})
end
it "should include any HTTP headers in the returned response" do
@request.options[:format] = :html
response = stub_response "Content"
response.initialize_http_header("key" => "value")
expect(@request.perform.headers).to eq({ "key" => ["value"] })
end
if "".respond_to?(:encoding)
context 'when body has ascii-8bit encoding' do
let(:response) { stub_response "Content".force_encoding('ascii-8bit') }
it "processes charset in content type properly" do
response.initialize_http_header("Content-Type" => "text/plain;charset = utf-8")
resp = @request.perform
expect(resp.body.encoding).to eq(Encoding.find("UTF-8"))
end
it "processes charset in content type properly if it has a different case" do
response.initialize_http_header("Content-Type" => "text/plain;CHARSET = utf-8")
resp = @request.perform
expect(resp.body.encoding).to eq(Encoding.find("UTF-8"))
end
it "processes quoted charset in content type properly" do
response.initialize_http_header("Content-Type" => "text/plain;charset = \"utf-8\"")
resp = @request.perform
expect(resp.body.encoding).to eq(Encoding.find("UTF-8"))
end
context 'when stubed body is frozen' do
let(:response) do
stub_response "Content".force_encoding('ascii-8bit').freeze
end
it 'processes frozen body correctly' do
response.initialize_http_header("Content-Type" => "text/plain;charset = utf-8")
resp = @request.perform
expect(resp.body.encoding).to eq(Encoding.find("UTF-8"))
end
end
end
it "should process response with a nil body" do
response = stub_response nil
response.initialize_http_header("Content-Type" => "text/html;charset=UTF-8")
resp = @request.perform
expect(resp.body).to be_nil
end
context 'when assume_utf16_is_big_endian is true' do
before { @request.options[:assume_utf16_is_big_endian] = true }
it "should process utf-16 charset with little endian bom correctly" do
response = stub_response "\xFF\xFEC\x00o\x00n\x00t\x00e\x00n\x00t\x00"
response.initialize_http_header("Content-Type" => "text/plain;charset = utf-16")
resp = @request.perform
expect(resp.body.encoding).to eq(Encoding.find("UTF-16LE"))
end
it 'processes stubbed frozen body correctly' do
response = stub_response "\xFF\xFEC\x00o\x00n\x00t\x00e\x00n\x00t\x00".freeze
response.initialize_http_header("Content-Type" => "text/plain;charset = utf-16")
resp = @request.perform
expect(resp.body.encoding).to eq(Encoding.find("UTF-16LE"))
end
end
it "should process utf-16 charset with big endian bom correctly" do
@request.options[:assume_utf16_is_big_endian] = false
response = stub_response "\xFE\xFF\x00C\x00o\x00n\x00t\x00e\x00n\x00t"
response.initialize_http_header("Content-Type" => "text/plain;charset = utf-16")
resp = @request.perform
expect(resp.body.encoding).to eq(Encoding.find("UTF-16BE"))
end
it "should assume utf-16 little endian if options has been chosen" do
@request.options[:assume_utf16_is_big_endian] = false
response = stub_response "C\x00o\x00n\x00t\x00e\x00n\x00t\x00"
response.initialize_http_header("Content-Type" => "text/plain;charset = utf-16")
resp = @request.perform
expect(resp.body.encoding).to eq(Encoding.find("UTF-16LE"))
end
it "should perform no encoding if the charset is not available" do
response = stub_response "Content"
response.initialize_http_header("Content-Type" => "text/plain;charset = utf-lols")
resp = @request.perform
# This encoding does not exist, thus the string should not be encodd with it
expect(resp.body).to eq("Content")
expect(resp.body.encoding).to eq("Content".encoding)
end
it "should perform no encoding if the content type is specified but no charset is specified" do
response = stub_response "Content"
response.initialize_http_header("Content-Type" => "text/plain")
resp = @request.perform
expect(resp.body).to eq("Content")
expect(resp.body.encoding).to eq("Content".encoding)
end
end
describe 'with non-200 responses' do
context "3xx responses" do
it 'returns a valid object for 304 not modified' do
stub_response '', 304
resp = @request.perform
expect(resp.code).to eq(304)
expect(resp.body).to eq('')
expect(resp).to be_nil
end
it "redirects if a 300 contains a location header" do
redirect = stub_response '', 300
redirect['location'] = 'http://foo.com/foo'
ok = stub_response('bar', 200)
allow(@http).to receive(:request).and_return(redirect, ok)
response = @request.perform
expect(response.request.base_uri.to_s).to eq("http://foo.com")
expect(response.request.path.to_s).to eq("http://foo.com/foo")
expect(response.request.uri.request_uri).to eq("/foo")
expect(response.request.uri.to_s).to eq("http://foo.com/foo")
expect(response.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "calls block given to perform with each redirect" do
@request = HTTParty::Request.new(Net::HTTP::Get, 'http://test.com/redirect', format: :xml)
stub_request(:get, 'http://test.com/redirect')
.to_return(
status: [300, 'REDIRECT'],
headers: { location: 'http://api.foo.com/v2' }
)
stub_request(:get, 'http://api.foo.com/v2')
.to_return(body: 'bar')
body = ""
@request.perform { |chunk| body += chunk }
expect(body.length).to eq(27)
end
it "redirects if a 300 contains a relative location header" do
redirect = stub_response '', 300
redirect['location'] = '/foo/bar'
ok = stub_response('bar', 200)
allow(@http).to receive(:request).and_return(redirect, ok)
response = @request.perform
expect(response.request.base_uri.to_s).to eq("http://api.foo.com")
expect(response.request.path.to_s).to eq("/foo/bar")
expect(response.request.uri.request_uri).to eq("/foo/bar")
expect(response.request.uri.to_s).to eq("http://api.foo.com/foo/bar")
expect(response.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "handles multiple redirects and relative location headers on different hosts" do
@request = HTTParty::Request.new(Net::HTTP::Get, 'http://test.com/redirect', format: :xml)
stub_request(:get, 'http://test.com/redirect')
.to_return(
status: [300, 'REDIRECT'],
headers: { location: "http://api.foo.com/v2" }
)
stub_request(:get, 'http://api.foo.com/v2')
.to_return(
status: [300, 'REDIRECT'],
headers: { location: '/v3' }
)
stub_request(:get, 'http://api.foo.com/v3')
.to_return(body: 'bar')
response = @request.perform
expect(response.request.base_uri.to_s).to eq("http://api.foo.com")
expect(response.request.path.to_s).to eq("/v3")
expect(response.request.uri.request_uri).to eq("/v3")
expect(response.request.uri.to_s).to eq("http://api.foo.com/v3")
expect(response.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "raises an error if redirect has duplicate location header" do
@request = HTTParty::Request.new(Net::HTTP::Get, 'http://test.com/redirect', format: :xml)
stub_request(:get, 'http://test.com/redirect')
.to_return(
status: [300, 'REDIRECT'],
headers: {
location: ['http://api.foo.com/v2', 'http://api.foo.com/v2']
}
)
expect {@request.perform}.to raise_error(HTTParty::DuplicateLocationHeader)
end
it "returns the HTTParty::Response when the 300 does not contain a location header" do
stub_response '', 300
expect(HTTParty::Response).to be === @request.perform
end
it "redirects including port" do
stub_request(:get, 'http://withport.com:3000/v1')
.to_return(
status: [301, 'Moved Permanently'],
headers: { location: 'http://withport.com:3000/v2' }
)
stub_request(:get, 'http://withport.com:3000/v2')
.to_return(status: 200)
request = HTTParty::Request.new(Net::HTTP::Get, 'http://withport.com:3000/v1')
response = request.perform
expect(response.request.base_uri.to_s).to eq("http://withport.com:3000")
end
end
it 'should return a valid object for 4xx response' do
stub_response 'yes', 401
resp = @request.perform
expect(resp.code).to eq(401)
expect(resp.body).to eq("yes")
expect(resp['foo']['bar']).to eq("yes")
end
it 'should return a valid object for 5xx response' do
stub_response 'error', 500
resp = @request.perform
expect(resp.code).to eq(500)
expect(resp.body).to eq("error")
expect(resp['foo']['bar']).to eq("error")
end
it "parses response lazily so codes can be checked prior" do
stub_response 'not xml', 500
@request.options[:format] = :xml
expect {
response = @request.perform
expect(response.code).to eq(500)
expect(response.body).to eq('not xml')
}.not_to raise_error
end
end
end
it "should not attempt to parse empty responses" do
[204, 304].each do |code|
stub_response "", code
@request.options[:format] = :xml
expect(@request.perform).to be_nil
end
end
it "should not fail for missing mime type" do
stub_response "Content for you"
@request.options[:format] = :html
expect(@request.perform.parsed_response).to eq('Content for you')
end
[300, 301, 302, 305].each do |code|
describe "a request that #{code} redirects" do
before(:each) do
@redirect = stub_response("", code)
@redirect['location'] = '/foo'
@ok = stub_response('bar', 200)
end
describe "once" do
before(:each) do
allow(@http).to receive(:request).and_return(@redirect, @ok)
end
it "should be handled by GET transparently" do
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by POST transparently" do
@request.http_method = Net::HTTP::Post
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by DELETE transparently" do
@request.http_method = Net::HTTP::Delete
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by MOVE transparently" do
@request.http_method = Net::HTTP::Move
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by COPY transparently" do
@request.http_method = Net::HTTP::Copy
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by PATCH transparently" do
@request.http_method = Net::HTTP::Patch
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by PUT transparently" do
@request.http_method = Net::HTTP::Put
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by HEAD transparently" do
@request.http_method = Net::HTTP::Head
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by OPTIONS transparently" do
@request.http_method = Net::HTTP::Options
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by MKCOL transparently" do
@request.http_method = Net::HTTP::Mkcol
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by LOCK transparently" do
@request.http_method = Net::HTTP::Lock
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by UNLOCK transparently" do
@request.http_method = Net::HTTP::Unlock
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should keep track of cookies between redirects" do
@redirect['Set-Cookie'] = 'foo=bar; name=value; HTTPOnly'
@request.perform
expect(@request.options[:headers]['Cookie']).to match(/foo=bar/)
expect(@request.options[:headers]['Cookie']).to match(/name=value/)
end
it 'should update cookies with redirects' do
@request.options[:headers] = {'Cookie' => 'foo=bar;'}
@redirect['Set-Cookie'] = 'foo=tar;'
@request.perform
expect(@request.options[:headers]['Cookie']).to match(/foo=tar/)
end
it 'should keep cookies between redirects' do
@request.options[:headers] = {'Cookie' => 'keep=me'}
@redirect['Set-Cookie'] = 'foo=tar;'
@request.perform
expect(@request.options[:headers]['Cookie']).to match(/keep=me/)
end
it "should handle multiple Set-Cookie headers between redirects" do
@redirect.add_field 'set-cookie', 'foo=bar; name=value; HTTPOnly'
@redirect.add_field 'set-cookie', 'one=1; two=2; HTTPOnly'
@request.perform
expect(@request.options[:headers]['Cookie']).to match(/foo=bar/)
expect(@request.options[:headers]['Cookie']).to match(/name=value/)
expect(@request.options[:headers]['Cookie']).to match(/one=1/)
expect(@request.options[:headers]['Cookie']).to match(/two=2/)
end
it 'should make resulting request a get request if it not already' do
@request.http_method = Net::HTTP::Delete
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
expect(@request.http_method).to eq(Net::HTTP::Get)
end
it 'should not make resulting request a get request if options[:maintain_method_across_redirects] is true' do
@request.options[:maintain_method_across_redirects] = true
@request.http_method = Net::HTTP::Delete
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
expect(@request.http_method).to eq(Net::HTTP::Delete)
end
it 'should log the redirection' do
logger_double = double
expect(logger_double).to receive(:info).twice
@request.options[:logger] = logger_double
@request.perform
end
end
describe "infinitely" do
before(:each) do
allow(@http).to receive(:request).and_return(@redirect)
end
it "should raise an exception" do
expect { @request.perform }.to raise_error(HTTParty::RedirectionTooDeep)
end
end
end
end
describe "a request that 303 redirects" do
before(:each) do
@redirect = stub_response("", 303)
@redirect['location'] = '/foo'
@ok = stub_response('bar', 200)
end
describe "once" do
before(:each) do
allow(@http).to receive(:request).and_return(@redirect, @ok)
end
it "should be handled by GET transparently" do
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by POST transparently" do
@request.http_method = Net::HTTP::Post
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by DELETE transparently" do
@request.http_method = Net::HTTP::Delete
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by MOVE transparently" do
@request.http_method = Net::HTTP::Move
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by COPY transparently" do
@request.http_method = Net::HTTP::Copy
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by PATCH transparently" do
@request.http_method = Net::HTTP::Patch
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by PUT transparently" do
@request.http_method = Net::HTTP::Put
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by HEAD transparently" do
@request.http_method = Net::HTTP::Head
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by OPTIONS transparently" do
@request.http_method = Net::HTTP::Options
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by MKCOL transparently" do
@request.http_method = Net::HTTP::Mkcol
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by LOCK transparently" do
@request.http_method = Net::HTTP::Lock
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by UNLOCK transparently" do
@request.http_method = Net::HTTP::Unlock
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should keep track of cookies between redirects" do
@redirect['Set-Cookie'] = 'foo=bar; name=value; HTTPOnly'
@request.perform
expect(@request.options[:headers]['Cookie']).to match(/foo=bar/)
expect(@request.options[:headers]['Cookie']).to match(/name=value/)
end
it 'should update cookies with redirects' do
@request.options[:headers] = {'Cookie' => 'foo=bar;'}
@redirect['Set-Cookie'] = 'foo=tar;'
@request.perform
expect(@request.options[:headers]['Cookie']).to match(/foo=tar/)
end
it 'should keep cookies between redirects' do
@request.options[:headers] = {'Cookie' => 'keep=me'}
@redirect['Set-Cookie'] = 'foo=tar;'
@request.perform
expect(@request.options[:headers]['Cookie']).to match(/keep=me/)
end
it "should handle multiple Set-Cookie headers between redirects" do
@redirect.add_field 'set-cookie', 'foo=bar; name=value; HTTPOnly'
@redirect.add_field 'set-cookie', 'one=1; two=2; HTTPOnly'
@request.perform
expect(@request.options[:headers]['Cookie']).to match(/foo=bar/)
expect(@request.options[:headers]['Cookie']).to match(/name=value/)
expect(@request.options[:headers]['Cookie']).to match(/one=1/)
expect(@request.options[:headers]['Cookie']).to match(/two=2/)
end
it 'should make resulting request a get request if it not already' do
@request.http_method = Net::HTTP::Delete
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
expect(@request.http_method).to eq(Net::HTTP::Get)
end
it 'should make resulting request a get request if options[:maintain_method_across_redirects] is false' do
@request.options[:maintain_method_across_redirects] = false
@request.http_method = Net::HTTP::Delete
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
expect(@request.http_method).to eq(Net::HTTP::Get)
end
it 'should make resulting request a get request if options[:maintain_method_across_redirects] is true but options[:resend_on_redirect] is false' do
@request.options[:maintain_method_across_redirects] = true
@request.options[:resend_on_redirect] = false
@request.http_method = Net::HTTP::Delete
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
expect(@request.http_method).to eq(Net::HTTP::Get)
end
it 'should not make resulting request a get request if options[:maintain_method_across_redirects] and options[:resend_on_redirect] is true' do
@request.options[:maintain_method_across_redirects] = true
@request.options[:resend_on_redirect] = true
@request.http_method = Net::HTTP::Delete
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
expect(@request.http_method).to eq(Net::HTTP::Delete)
end
it 'should log the redirection' do
logger_double = double
expect(logger_double).to receive(:info).twice
@request.options[:logger] = logger_double
@request.perform
end
end
describe "infinitely" do
before(:each) do
allow(@http).to receive(:request).and_return(@redirect)
end
it "should raise an exception" do
expect { @request.perform }.to raise_error(HTTParty::RedirectionTooDeep)
end
end
end
describe "a request that returns 304" do
before(:each) do
@redirect = stub_response("", 304)
@redirect['location'] = '/foo'
end
before(:each) do
allow(@http).to receive(:request).and_return(@redirect)
end
it "should report 304 with a GET request" do
expect(@request.perform.code).to eq(304)
end
it "should report 304 with a POST request" do
@request.http_method = Net::HTTP::Post
expect(@request.perform.code).to eq(304)
end
it "should report 304 with a DELETE request" do
@request.http_method = Net::HTTP::Delete
expect(@request.perform.code).to eq(304)
end
it "should report 304 with a MOVE request" do
@request.http_method = Net::HTTP::Move
expect(@request.perform.code).to eq(304)
end
it "should report 304 with a COPY request" do
@request.http_method = Net::HTTP::Copy
expect(@request.perform.code).to eq(304)
end
it "should report 304 with a PATCH request" do
@request.http_method = Net::HTTP::Patch
expect(@request.perform.code).to eq(304)
end
it "should report 304 with a PUT request" do
@request.http_method = Net::HTTP::Put
expect(@request.perform.code).to eq(304)
end
it "should report 304 with a HEAD request" do
@request.http_method = Net::HTTP::Head
expect(@request.perform.code).to eq(304)
end
it "should report 304 with a OPTIONS request" do
@request.http_method = Net::HTTP::Options
expect(@request.perform.code).to eq(304)
end
it "should report 304 with a MKCOL request" do
@request.http_method = Net::HTTP::Mkcol
expect(@request.perform.code).to eq(304)
end
it "should be handled by LOCK transparently" do
@request.http_method = Net::HTTP::Lock
expect(@request.perform.code).to eq(304)
end
it "should be handled by UNLOCK transparently" do
@request.http_method = Net::HTTP::Unlock
expect(@request.perform.code).to eq(304)
end
it 'should not log the redirection' do
logger_double = double
expect(logger_double).to receive(:info).once
@request.options[:logger] = logger_double
@request.perform
end
end
[307, 308].each do |code|
describe "a request that #{code} redirects" do
before(:each) do
@redirect = stub_response("", code)
@redirect['location'] = '/foo'
@ok = stub_response('bar', 200)
end
describe "once" do
before(:each) do
allow(@http).to receive(:request).and_return(@redirect, @ok)
end
it "should be handled by GET transparently" do
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by POST transparently" do
@request.http_method = Net::HTTP::Post
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by DELETE transparently" do
@request.http_method = Net::HTTP::Delete
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by MOVE transparently" do
@request.http_method = Net::HTTP::Move
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by COPY transparently" do
@request.http_method = Net::HTTP::Copy
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by PATCH transparently" do
@request.http_method = Net::HTTP::Patch
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by PUT transparently" do
@request.http_method = Net::HTTP::Put
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by HEAD transparently" do
@request.http_method = Net::HTTP::Head
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by OPTIONS transparently" do
@request.http_method = Net::HTTP::Options
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by MKCOL transparently" do
@request.http_method = Net::HTTP::Mkcol
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by LOCK transparently" do
@request.http_method = Net::HTTP::Lock
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should be handled by UNLOCK transparently" do
@request.http_method = Net::HTTP::Unlock
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
end
it "should keep track of cookies between redirects" do
@redirect['Set-Cookie'] = 'foo=bar; name=value; HTTPOnly'
@request.perform
expect(@request.options[:headers]['Cookie']).to match(/foo=bar/)
expect(@request.options[:headers]['Cookie']).to match(/name=value/)
end
it 'should update cookies with redirects' do
@request.options[:headers] = {'Cookie' => 'foo=bar;'}
@redirect['Set-Cookie'] = 'foo=tar;'
@request.perform
expect(@request.options[:headers]['Cookie']).to match(/foo=tar/)
end
it 'should keep cookies between redirects' do
@request.options[:headers] = {'Cookie' => 'keep=me'}
@redirect['Set-Cookie'] = 'foo=tar;'
@request.perform
expect(@request.options[:headers]['Cookie']).to match(/keep=me/)
end
it "should handle multiple Set-Cookie headers between redirects" do
@redirect.add_field 'set-cookie', 'foo=bar; name=value; HTTPOnly'
@redirect.add_field 'set-cookie', 'one=1; two=2; HTTPOnly'
@request.perform
expect(@request.options[:headers]['Cookie']).to match(/foo=bar/)
expect(@request.options[:headers]['Cookie']).to match(/name=value/)
expect(@request.options[:headers]['Cookie']).to match(/one=1/)
expect(@request.options[:headers]['Cookie']).to match(/two=2/)
end
it 'should maintain method in resulting request' do
@request.http_method = Net::HTTP::Delete
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
expect(@request.http_method).to eq(Net::HTTP::Delete)
end
it 'should maintain method in resulting request if options[:maintain_method_across_redirects] is false' do
@request.options[:maintain_method_across_redirects] = false
@request.http_method = Net::HTTP::Delete
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
expect(@request.http_method).to eq(Net::HTTP::Delete)
end
it 'should maintain method in resulting request if options[:maintain_method_across_redirects] is true' do
@request.options[:maintain_method_across_redirects] = true
@request.http_method = Net::HTTP::Delete
expect(@request.perform.parsed_response).to eq({"hash" => {"foo" => "bar"}})
expect(@request.http_method).to eq(Net::HTTP::Delete)
end
it 'should log the redirection' do
logger_double = double
expect(logger_double).to receive(:info).twice
@request.options[:logger] = logger_double
@request.perform
end
end
describe "infinitely" do
before(:each) do
allow(@http).to receive(:request).and_return(@redirect)
end
it "should raise an exception" do
expect { @request.perform }.to raise_error(HTTParty::RedirectionTooDeep)
end
end
end
end
describe "#send_authorization_header?" do
context "basic_auth" do
before do
@credentials = { username: "username", password: "password" }
@authorization = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
@request.options[:basic_auth] = @credentials
@redirect = stub_response("", 302)
@ok = stub_response('bar', 200)
end
before(:each) do
allow(@http).to receive(:request).and_return(@redirect, @ok)
end
it "should not send Authorization header when redirecting to a different host" do
@redirect['location'] = 'http://example.com/'
@request.perform
@request.send(:setup_raw_request)
expect(@request.instance_variable_get(:@raw_request)['authorization']).to be_nil
end
it "should send Authorization header when redirecting to a relative path" do
@redirect['location'] = '/v3'
@request.perform
@request.send(:setup_raw_request)
expect(@request.instance_variable_get(:@raw_request)['authorization']).to eq(@authorization)
end
it "should send Authorization header when redirecting to the same host" do
@redirect['location'] = 'http://api.foo.com/v2'
@request.perform
@request.send(:setup_raw_request)
expect(@request.instance_variable_get(:@raw_request)['authorization']).to eq(@authorization)
end
it "should send Authorization header when redirecting to a different port on the same host" do
@redirect['location'] = 'http://api.foo.com:3000/v3'
@request.perform
@request.send(:setup_raw_request)
expect(@request.instance_variable_get(:@raw_request)['authorization']).to eq(@authorization)
end
end
end
context "with POST http method" do
it "should raise argument error if query is not a hash" do
expect {
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', format: :xml, query: 'astring').perform
}.to raise_error(ArgumentError)
end
end
describe "argument validation" do
it "should raise argument error if basic_auth and digest_auth are both present" do
expect {
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', basic_auth: {}, digest_auth: {}).perform
}.to raise_error(ArgumentError, "only one authentication method, :basic_auth or :digest_auth may be used at a time")
end
it "should raise argument error if basic_auth is not a hash" do
expect {
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', basic_auth: %w(foo bar)).perform
}.to raise_error(ArgumentError, ":basic_auth must be a hash")
end
it "should raise argument error if digest_auth is not a hash" do
expect {
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', digest_auth: %w(foo bar)).perform
}.to raise_error(ArgumentError, ":digest_auth must be a hash")
end
it "should raise argument error if headers is not a hash" do
expect {
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', headers: %w(foo bar)).perform
}.to raise_error(ArgumentError, ":headers must be a hash")
end
it "should raise argument error if options method is not http accepted method" do
expect {
HTTParty::Request.new('SuperPost', 'http://api.foo.com/v1').perform
}.to raise_error(ArgumentError, "only get, post, patch, put, delete, head, and options methods are supported")
end
it "should raise argument error if http method is post and query is not hash" do
expect {
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', query: "message: hello").perform
}.to raise_error(ArgumentError, ":query must be hash if using HTTP Post")
end
it "should raise RedirectionTooDeep error if limit is negative" do
expect {
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', limit: -1).perform
}.to raise_error(HTTParty::RedirectionTooDeep, 'HTTP redirects too deep')
end
end
context 'with Accept-Encoding header' do
it 'should disable content decoding if present' do
request = HTTParty::Request.new(Net::HTTP::Get, 'http://api.foo.com/v1', headers:{'Accept-Encoding' => 'custom'})
request.send(:setup_raw_request)
expect(request.instance_variable_get(:@raw_request).decode_content).to eq(false)
end
it 'should disable content decoding if present and lowercase' do
request = HTTParty::Request.new(Net::HTTP::Get, 'http://api.foo.com/v1', headers:{'accept-encoding' => 'custom'})
request.send(:setup_raw_request)
expect(request.instance_variable_get(:@raw_request).decode_content).to eq(false)
end
it 'should disable content decoding if present' do
request = HTTParty::Request.new(Net::HTTP::Get, 'http://api.foo.com/v1')
request.send(:setup_raw_request)
expect(request.instance_variable_get(:@raw_request).decode_content).to eq(true)
end
end
end