require File.expand_path(File.join(File.dirname(__FILE__), '..', '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 "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 HTTPParty::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 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 it "should use digest auth when configured" do FakeWeb.register_uri(:get, "http://api.foo.com/v1", www_authenticate: 'Digest realm="Log Viewer", qop="auth", nonce="2CA0EC6B0E126C4800E56BA0C0003D3C", opaque="5ccc069c403ebaf9f0171e9517f40e41", stale=false') @request.options[:digest_auth] = {username: 'foobar', password: 'secret'} @request.send(:setup_raw_request) raw_request = @request.instance_variable_get(:@raw_request) expect(raw_request.instance_variable_get(:@header)['Authorization']).not_to be_nil end it "should use the right http method for digest authentication" do @post_request = HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', format: :xml) FakeWeb.register_uri(:post, "http://api.foo.com/v1", {}) http = @post_request.send(:http) expect(@post_request).to receive(:http).and_return(http) expect(http).not_to receive(:head).with({'www-authenticate' => nil}) @post_request.options[:digest_auth] = {username: 'foobar', password: 'secret'} @post_request.send(:setup_raw_request) end it 'should maintain cookies returned from setup_digest_auth' do FakeWeb.register_uri( :get, "http://api.foo.com/v1", set_cookie: 'custom-cookie=1234567', www_authenticate: 'Digest realm="Log Viewer", qop="auth", nonce="2CA0EC6B0E126C4800E56BA0C0003D3C", opaque="5ccc069c403ebaf9f0171e9517f40e41", stale=false' ) @request.options[:digest_auth] = {username: 'foobar', password: 'secret'} @request.send(:setup_raw_request) raw_request = @request.instance_variable_get(:@raw_request) expect(raw_request.instance_variable_get(:@header)['cookie']).to eql ["custom-cookie=1234567"] end it 'should merge cookies from setup_digest_auth and request' do FakeWeb.register_uri( :get, "http://api.foo.com/v1", set_cookie: 'custom-cookie=1234567', www_authenticate: 'Digest realm="Log Viewer", qop="auth", nonce="2CA0EC6B0E126C4800E56BA0C0003D3C", opaque="5ccc069c403ebaf9f0171e9517f40e41", stale=false' ) @request.options[:digest_auth] = {username: 'foobar', password: 'secret'} @request.options[:headers] = {'cookie' => 'request-cookie=test'} @request.send(:setup_raw_request) raw_request = @request.instance_variable_get(:@raw_request) expect(raw_request.instance_variable_get(:@header)['cookie']).to eql ['request-cookie=test', 'custom-cookie=1234567'] 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 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 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| ""} @request.options[:query_string_normalizer] = empty_proc @request.options[:query] = {foo: :bar} expect(CGI.unescape(@request.uri.query)).to eq("") 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 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/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 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 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) it "should process charset in content type properly" do response = stub_response "Content" 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 "should process charset in content type properly if it has a different case" do response = stub_response "Content" 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 "should process quoted charset in content type properly" do response = stub_response "Content" 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 "should process utf-16 charset with little endian bom correctly" do @request.options[:assume_utf16_is_big_endian] = true 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 "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 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) FakeWeb.register_uri(:get, "http://test.com/redirect", status: [300, "REDIRECT"], location: "http://api.foo.com/v2") FakeWeb.register_uri(:get, "http://api.foo.com/v2", body: "bar") body = "" response = @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) FakeWeb.register_uri(:get, "http://test.com/redirect", status: [300, "REDIRECT"], location: "http://api.foo.com/v2") FakeWeb.register_uri(:get, "http://api.foo.com/v2", status: [300, "REDIRECT"], location: "/v3") FakeWeb.register_uri(:get, "http://api.foo.com/v3", 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 "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 FakeWeb.register_uri(:get, "http://withport.com:3000/v1", status: [301, "Moved Permanently"], location: "http://withport.com:3000/v2") FakeWeb.register_uri(:get, "http://withport.com:3000/v2", 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 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 rediects' 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 rediects' 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 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 rediects' 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 rediects' 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 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 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 rediects' 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 rediects' 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 "#handle_deflation" do context "context-encoding" do before do @request.options[:format] = :html @last_response = double allow(@last_response).to receive(:body).and_return('') end it "should inflate the gzipped body with content-encoding: gzip" do allow(@last_response).to receive(:[]).with("content-encoding").and_return("gzip") allow(@request).to receive(:last_response).and_return(@last_response) expect(Zlib::GzipReader).to receive(:new).and_return(StringIO.new('')) expect(@request.last_response).to receive(:delete).with('content-encoding') @request.send(:handle_deflation) end it "should inflate the gzipped body with content-encoding: x-gzip" do allow(@last_response).to receive(:[]).with("content-encoding").and_return("x-gzip") allow(@request).to receive(:last_response).and_return(@last_response) expect(Zlib::GzipReader).to receive(:new).and_return(StringIO.new('')) expect(@request.last_response).to receive(:delete).with('content-encoding') @request.send(:handle_deflation) end it "should inflate the deflated body" do allow(@last_response).to receive(:[]).with("content-encoding").and_return("deflate") allow(@request).to receive(:last_response).and_return(@last_response) expect(Zlib::Inflate).to receive(:inflate).and_return('') expect(@request.last_response).to receive(:delete).with('content-encoding') @request.send(:handle_deflation) 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 end