httparty2 0.7.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +9 -0
- data/Gemfile +6 -0
- data/History +253 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +54 -0
- data/Rakefile +13 -0
- data/bin/httparty +108 -0
- data/cucumber.yml +1 -0
- data/examples/aaws.rb +32 -0
- data/examples/basic.rb +32 -0
- data/examples/custom_parsers.rb +67 -0
- data/examples/delicious.rb +37 -0
- data/examples/google.rb +16 -0
- data/examples/rubyurl.rb +14 -0
- data/examples/tripit_sign_in.rb +33 -0
- data/examples/twitter.rb +31 -0
- data/examples/whoismyrep.rb +10 -0
- data/features/basic_authentication.feature +20 -0
- data/features/command_line.feature +7 -0
- data/features/deals_with_http_error_codes.feature +26 -0
- data/features/digest_authentication.feature +20 -0
- data/features/handles_compressed_responses.feature +19 -0
- data/features/handles_multiple_formats.feature +34 -0
- data/features/steps/env.rb +22 -0
- data/features/steps/httparty_response_steps.rb +26 -0
- data/features/steps/httparty_steps.rb +27 -0
- data/features/steps/mongrel_helper.rb +94 -0
- data/features/steps/remote_service_steps.rb +69 -0
- data/features/supports_redirection.feature +22 -0
- data/features/supports_timeout_option.feature +13 -0
- data/httparty.gemspec +31 -0
- data/lib/httparty.rb +455 -0
- data/lib/httparty/cookie_hash.rb +22 -0
- data/lib/httparty/core_extensions.rb +9 -0
- data/lib/httparty/exceptions.rb +26 -0
- data/lib/httparty/module_inheritable_attributes.rb +34 -0
- data/lib/httparty/net_digest_auth.rb +71 -0
- data/lib/httparty/parser.rb +140 -0
- data/lib/httparty/request.rb +252 -0
- data/lib/httparty/response.rb +85 -0
- data/lib/httparty/version.rb +3 -0
- data/spec/fixtures/delicious.xml +23 -0
- data/spec/fixtures/empty.xml +0 -0
- data/spec/fixtures/google.html +3 -0
- data/spec/fixtures/ssl/generate.sh +29 -0
- data/spec/fixtures/ssl/generated/1fe462c2.0 +16 -0
- data/spec/fixtures/ssl/generated/bogushost.crt +13 -0
- data/spec/fixtures/ssl/generated/ca.crt +16 -0
- data/spec/fixtures/ssl/generated/ca.key +15 -0
- data/spec/fixtures/ssl/generated/selfsigned.crt +14 -0
- data/spec/fixtures/ssl/generated/server.crt +13 -0
- data/spec/fixtures/ssl/generated/server.key +15 -0
- data/spec/fixtures/ssl/openssl-exts.cnf +9 -0
- data/spec/fixtures/twitter.json +1 -0
- data/spec/fixtures/twitter.xml +403 -0
- data/spec/fixtures/undefined_method_add_node_for_nil.xml +2 -0
- data/spec/httparty/cookie_hash_spec.rb +71 -0
- data/spec/httparty/net_digest_auth_spec.rb +93 -0
- data/spec/httparty/parser_spec.rb +155 -0
- data/spec/httparty/request_spec.rb +496 -0
- data/spec/httparty/response_spec.rb +193 -0
- data/spec/httparty/ssl_spec.rb +54 -0
- data/spec/httparty_spec.rb +621 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/support/ssl_test_helper.rb +25 -0
- data/spec/support/ssl_test_server.rb +69 -0
- data/spec/support/stub_response.rb +30 -0
- data/website/css/common.css +47 -0
- data/website/index.html +73 -0
- metadata +206 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe HTTParty::CookieHash do
|
4
|
+
before(:each) do
|
5
|
+
@cookie_hash = HTTParty::CookieHash.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#add_cookies" do
|
9
|
+
|
10
|
+
describe "with a hash" do
|
11
|
+
it "should add new key/value pairs to the hash" do
|
12
|
+
@cookie_hash.add_cookies(:foo => "bar")
|
13
|
+
@cookie_hash.add_cookies(:rofl => "copter")
|
14
|
+
@cookie_hash.length.should eql(2)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should overwrite any existing key" do
|
18
|
+
@cookie_hash.add_cookies(:foo => "bar")
|
19
|
+
@cookie_hash.add_cookies(:foo => "copter")
|
20
|
+
@cookie_hash.length.should eql(1)
|
21
|
+
@cookie_hash[:foo].should eql("copter")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "with a string" do
|
26
|
+
it "should add new key/value pairs to the hash" do
|
27
|
+
@cookie_hash.add_cookies("first=one; second=two; third")
|
28
|
+
@cookie_hash[:first].should == 'one'
|
29
|
+
@cookie_hash[:second].should == 'two'
|
30
|
+
@cookie_hash[:third].should == nil
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should overwrite any existing key" do
|
34
|
+
@cookie_hash[:foo] = 'bar'
|
35
|
+
@cookie_hash.add_cookies("foo=tar")
|
36
|
+
@cookie_hash.length.should eql(1)
|
37
|
+
@cookie_hash[:foo].should eql("tar")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'with other class' do
|
42
|
+
it "should error" do
|
43
|
+
lambda {
|
44
|
+
@cookie_hash.add_cookies(Array.new)
|
45
|
+
}.should raise_error
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# The regexen are required because Hashes aren't ordered, so a test against
|
51
|
+
# a hardcoded string was randomly failing.
|
52
|
+
describe "#to_cookie_string" do
|
53
|
+
before(:each) do
|
54
|
+
@cookie_hash.add_cookies(:foo => "bar")
|
55
|
+
@cookie_hash.add_cookies(:rofl => "copter")
|
56
|
+
@s = @cookie_hash.to_cookie_string
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should format the key/value pairs, delimited by semi-colons" do
|
60
|
+
@s.should match(/foo=bar/)
|
61
|
+
@s.should match(/rofl=copter/)
|
62
|
+
@s.should match(/^\w+=\w+; \w+=\w+$/)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should not include client side only cookies" do
|
66
|
+
@cookie_hash.add_cookies(:path => "/")
|
67
|
+
@s = @cookie_hash.to_cookie_string
|
68
|
+
@s.should_not match(/path=\//)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe Net::HTTPHeader::DigestAuthenticator do
|
4
|
+
def setup_digest(response)
|
5
|
+
digest = Net::HTTPHeader::DigestAuthenticator.new("Mufasa",
|
6
|
+
"Circle Of Life", "GET", "/dir/index.html", response)
|
7
|
+
digest.stub(:random).and_return("deadbeef")
|
8
|
+
Digest::MD5.stub(:hexdigest) { |str| "md5(#{str})" }
|
9
|
+
digest
|
10
|
+
end
|
11
|
+
|
12
|
+
def authorization_header
|
13
|
+
@digest.authorization_header.join(", ")
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
context "with specified quality of protection (qop)" do
|
18
|
+
before do
|
19
|
+
@digest = setup_digest({'www-authenticate' =>
|
20
|
+
'Digest realm="myhost@testrealm.com", nonce="NONCE", qop="auth"'})
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should set prefix" do
|
24
|
+
authorization_header.should =~ /^Digest /
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should set username" do
|
28
|
+
authorization_header.should include(%Q(username="Mufasa"))
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should set digest-uri" do
|
32
|
+
authorization_header.should include(%Q(uri="/dir/index.html"))
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should set qop" do
|
36
|
+
authorization_header.should include(%Q(qop="auth"))
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should set cnonce" do
|
40
|
+
authorization_header.should include(%Q(cnonce="md5(deadbeef)"))
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should set nonce-count" do
|
44
|
+
authorization_header.should include(%Q(nc="0"))
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should set response" do
|
48
|
+
request_digest =
|
49
|
+
"md5(md5(Mufasa:myhost@testrealm.com:Circle Of Life)" +
|
50
|
+
":NONCE:0:md5(deadbeef):auth:md5(GET:/dir/index.html))"
|
51
|
+
authorization_header.should include(%Q(response="#{request_digest}"))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
context "with unspecified quality of protection (qop)" do
|
57
|
+
before do
|
58
|
+
@digest = setup_digest({'www-authenticate' =>
|
59
|
+
'Digest realm="myhost@testrealm.com", nonce="NONCE"'})
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should set prefix" do
|
63
|
+
authorization_header.should =~ /^Digest /
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should set username" do
|
67
|
+
authorization_header.should include(%Q(username="Mufasa"))
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should set digest-uri" do
|
71
|
+
authorization_header.should include(%Q(uri="/dir/index.html"))
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should not set qop" do
|
75
|
+
authorization_header.should_not include(%Q(qop=))
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should not set cnonce" do
|
79
|
+
authorization_header.should_not include(%Q(cnonce=))
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should not set nonce-count" do
|
83
|
+
authorization_header.should_not include(%Q(nc=))
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should set response" do
|
87
|
+
request_digest =
|
88
|
+
"md5(md5(Mufasa:myhost@testrealm.com:Circle Of Life)" +
|
89
|
+
":NONCE:md5(GET:/dir/index.html))"
|
90
|
+
authorization_header.should include(%Q(response="#{request_digest}"))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe HTTParty::Parser do
|
4
|
+
describe ".SupportedFormats" do
|
5
|
+
it "returns a hash" do
|
6
|
+
HTTParty::Parser::SupportedFormats.should be_instance_of(Hash)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ".call" do
|
11
|
+
it "generates an HTTParty::Parser instance with the given body and format" do
|
12
|
+
HTTParty::Parser.should_receive(:new).with('body', :plain).and_return(stub(:parse => nil))
|
13
|
+
HTTParty::Parser.call('body', :plain)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "calls #parse on the parser" do
|
17
|
+
parser = mock('Parser')
|
18
|
+
parser.should_receive(:parse)
|
19
|
+
HTTParty::Parser.stub(:new => parser)
|
20
|
+
parser = HTTParty::Parser.call('body', :plain)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".formats" do
|
25
|
+
it "returns the SupportedFormats constant" do
|
26
|
+
HTTParty::Parser.formats.should == HTTParty::Parser::SupportedFormats
|
27
|
+
end
|
28
|
+
|
29
|
+
it "returns the SupportedFormats constant for subclasses" do
|
30
|
+
class MyParser < HTTParty::Parser
|
31
|
+
SupportedFormats = {"application/atom+xml" => :atom}
|
32
|
+
end
|
33
|
+
MyParser.formats.should == {"application/atom+xml" => :atom}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe ".format_from_mimetype" do
|
38
|
+
it "returns a symbol representing the format mimetype" do
|
39
|
+
HTTParty::Parser.format_from_mimetype("text/plain").should == :plain
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns nil when the mimetype is not supported" do
|
43
|
+
HTTParty::Parser.format_from_mimetype("application/atom+xml").should be_nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe ".supported_formats" do
|
48
|
+
it "returns a unique set of supported formats represented by symbols" do
|
49
|
+
HTTParty::Parser.supported_formats.should == HTTParty::Parser::SupportedFormats.values.uniq
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe ".supports_format?" do
|
54
|
+
it "returns true for a supported format" do
|
55
|
+
HTTParty::Parser.stub(:supported_formats => [:json])
|
56
|
+
HTTParty::Parser.supports_format?(:json).should be_true
|
57
|
+
end
|
58
|
+
|
59
|
+
it "returns false for an unsupported format" do
|
60
|
+
HTTParty::Parser.stub(:supported_formats => [])
|
61
|
+
HTTParty::Parser.supports_format?(:json).should be_false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "#parse" do
|
66
|
+
before do
|
67
|
+
@parser = HTTParty::Parser.new('body', :json)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "attempts to parse supported formats" do
|
71
|
+
@parser.stub(:supports_format? => true)
|
72
|
+
@parser.should_receive(:parse_supported_format)
|
73
|
+
@parser.parse
|
74
|
+
end
|
75
|
+
|
76
|
+
it "returns the unparsed body when the format is unsupported" do
|
77
|
+
@parser.stub(:supports_format? => false)
|
78
|
+
@parser.parse.should == @parser.body
|
79
|
+
end
|
80
|
+
|
81
|
+
it "returns nil for an empty body" do
|
82
|
+
@parser.stub(:body => '')
|
83
|
+
@parser.parse.should be_nil
|
84
|
+
end
|
85
|
+
|
86
|
+
it "returns nil for a nil body" do
|
87
|
+
@parser.stub(:body => nil)
|
88
|
+
@parser.parse.should be_nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#supports_format?" do
|
93
|
+
it "utilizes the class method to determine if the format is supported" do
|
94
|
+
HTTParty::Parser.should_receive(:supports_format?).with(:json)
|
95
|
+
parser = HTTParty::Parser.new('body', :json)
|
96
|
+
parser.send(:supports_format?)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "#parse_supported_format" do
|
101
|
+
it "calls the parser for the given format" do
|
102
|
+
parser = HTTParty::Parser.new('body', :json)
|
103
|
+
parser.should_receive(:json)
|
104
|
+
parser.send(:parse_supported_format)
|
105
|
+
end
|
106
|
+
|
107
|
+
context "when a parsing method does not exist for the given format" do
|
108
|
+
it "raises an exception" do
|
109
|
+
parser = HTTParty::Parser.new('body', :atom)
|
110
|
+
expect do
|
111
|
+
parser.send(:parse_supported_format)
|
112
|
+
end.to raise_error(NotImplementedError, "HTTParty::Parser has not implemented a parsing method for the :atom format.")
|
113
|
+
end
|
114
|
+
|
115
|
+
it "raises a useful exception message for subclasses" do
|
116
|
+
atom_parser = Class.new(HTTParty::Parser) do
|
117
|
+
def self.name; 'AtomParser'; end
|
118
|
+
end
|
119
|
+
parser = atom_parser.new 'body', :atom
|
120
|
+
expect do
|
121
|
+
parser.send(:parse_supported_format)
|
122
|
+
end.to raise_error(NotImplementedError, "AtomParser has not implemented a parsing method for the :atom format.")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "parsers" do
|
128
|
+
subject do
|
129
|
+
HTTParty::Parser.new('body', nil)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "parses xml with multi_xml" do
|
133
|
+
MultiXml.should_receive(:parse).with('body')
|
134
|
+
subject.send(:xml)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "parses json with multi_json" do
|
138
|
+
MultiJson.should_receive(:decode).with('body')
|
139
|
+
subject.send(:json)
|
140
|
+
end
|
141
|
+
|
142
|
+
it "parses yaml" do
|
143
|
+
YAML.should_receive(:load).with('body')
|
144
|
+
subject.send(:yaml)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "parses html by simply returning the body" do
|
148
|
+
subject.send(:html).should == 'body'
|
149
|
+
end
|
150
|
+
|
151
|
+
it "parses plain text by simply returning the body" do
|
152
|
+
subject.send(:plain).should == 'body'
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,496 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
describe HTTParty::Request do
|
4
|
+
before do
|
5
|
+
@request = HTTParty::Request.new(Net::HTTP::Get, 'http://api.foo.com/v1', :format => :xml)
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "::NON_RAILS_QUERY_STRING_NORMALIZER" do
|
9
|
+
let(:normalizer) { HTTParty::Request::NON_RAILS_QUERY_STRING_NORMALIZER }
|
10
|
+
|
11
|
+
it "doesn't modify strings" do
|
12
|
+
query_string = normalizer["foo=bar&foo=baz"]
|
13
|
+
URI.unescape(query_string).should == "foo=bar&foo=baz"
|
14
|
+
end
|
15
|
+
|
16
|
+
context "when the query is an array" do
|
17
|
+
it "doesn't include brackets" do
|
18
|
+
query_string = normalizer[{:page => 1, :foo => %w(bar baz)}]
|
19
|
+
URI.unescape(query_string).should == "foo=bar&foo=baz&page=1"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "URI encodes array values" do
|
23
|
+
query_string = normalizer[{:people => ["Bob Marley", "Tim & Jon"]}]
|
24
|
+
query_string.should == "people=Bob%20Marley&people=Tim%20%26%20Jon"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when the query is a hash" do
|
29
|
+
it "correctly handles nil values" do
|
30
|
+
query_string = normalizer[{:page => 1, :per_page => nil}]
|
31
|
+
query_string.should == "page=1&per_page"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "initialization" do
|
37
|
+
it "sets parser to HTTParty::Parser" do
|
38
|
+
request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com')
|
39
|
+
request.parser.should == HTTParty::Parser
|
40
|
+
end
|
41
|
+
|
42
|
+
it "sets parser to the optional parser" do
|
43
|
+
my_parser = lambda {}
|
44
|
+
request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com', :parser => my_parser)
|
45
|
+
request.parser.should == my_parser
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#format" do
|
50
|
+
context "request yet to be made" do
|
51
|
+
it "returns format option" do
|
52
|
+
request = HTTParty::Request.new 'get', '/', :format => :xml
|
53
|
+
request.format.should == :xml
|
54
|
+
end
|
55
|
+
|
56
|
+
it "returns nil format" do
|
57
|
+
request = HTTParty::Request.new 'get', '/'
|
58
|
+
request.format.should be_nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "request has been made" do
|
63
|
+
it "returns format option" do
|
64
|
+
request = HTTParty::Request.new 'get', '/', :format => :xml
|
65
|
+
request.last_response = stub
|
66
|
+
request.format.should == :xml
|
67
|
+
end
|
68
|
+
|
69
|
+
it "returns the content-type from the last response when the option is not set" do
|
70
|
+
request = HTTParty::Request.new 'get', '/'
|
71
|
+
response = stub
|
72
|
+
response.should_receive(:[]).with('content-type').and_return('text/json')
|
73
|
+
request.last_response = response
|
74
|
+
request.format.should == :json
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
context "options" do
|
81
|
+
it "should use basic auth when configured" do
|
82
|
+
@request.options[:basic_auth] = {:username => 'foobar', :password => 'secret'}
|
83
|
+
@request.send(:setup_raw_request)
|
84
|
+
@request.instance_variable_get(:@raw_request)['authorization'].should_not be_nil
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should use digest auth when configured" do
|
88
|
+
FakeWeb.register_uri(:head, "http://api.foo.com/v1",
|
89
|
+
:www_authenticate => 'Digest realm="Log Viewer", qop="auth", nonce="2CA0EC6B0E126C4800E56BA0C0003D3C", opaque="5ccc069c403ebaf9f0171e9517f40e41", stale=false')
|
90
|
+
|
91
|
+
@request.options[:digest_auth] = {:username => 'foobar', :password => 'secret'}
|
92
|
+
@request.send(:setup_raw_request)
|
93
|
+
|
94
|
+
raw_request = @request.instance_variable_get(:@raw_request)
|
95
|
+
raw_request.instance_variable_get(:@header)['Authorization'].should_not be_nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "#uri" do
|
100
|
+
context "query strings" do
|
101
|
+
it "does not add an empty query string when default_params are blank" do
|
102
|
+
@request.options[:default_params] = {}
|
103
|
+
@request.uri.query.should be_nil
|
104
|
+
end
|
105
|
+
|
106
|
+
it "respects the query string normalization proc" do
|
107
|
+
empty_proc = lambda {|qs| ""}
|
108
|
+
@request.options[:query_string_normalizer] = empty_proc
|
109
|
+
@request.options[:query] = {:foo => :bar}
|
110
|
+
URI.unescape(@request.uri.query).should == ""
|
111
|
+
end
|
112
|
+
|
113
|
+
context "when representing an array" do
|
114
|
+
it "returns a Rails style query string" do
|
115
|
+
@request.options[:query] = {:foo => %w(bar baz)}
|
116
|
+
URI.unescape(@request.uri.query).should == "foo[]=bar&foo[]=baz"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "#setup_raw_request" do
|
124
|
+
context "when query_string_normalizer is set" do
|
125
|
+
it "sets the body to the return value of the proc" do
|
126
|
+
@request.options[:query_string_normalizer] = HTTParty::Request::NON_RAILS_QUERY_STRING_NORMALIZER
|
127
|
+
@request.options[:body] = {:page => 1, :foo => %w(bar baz)}
|
128
|
+
@request.send(:setup_raw_request)
|
129
|
+
body = @request.instance_variable_get(:@raw_request).body
|
130
|
+
URI.unescape(body).should == "foo=bar&foo=baz&page=1"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe 'http' do
|
136
|
+
it "should use ssl for port 443" do
|
137
|
+
request = HTTParty::Request.new(Net::HTTP::Get, 'https://api.foo.com/v1:443')
|
138
|
+
request.send(:http).use_ssl?.should == true
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'should not use ssl for port 80' do
|
142
|
+
request = HTTParty::Request.new(Net::HTTP::Get, 'http://foobar.com')
|
143
|
+
request.send(:http).use_ssl?.should == false
|
144
|
+
end
|
145
|
+
|
146
|
+
it "uses ssl for https scheme with default port" do
|
147
|
+
request = HTTParty::Request.new(Net::HTTP::Get, 'https://foobar.com')
|
148
|
+
request.send(:http).use_ssl?.should == true
|
149
|
+
end
|
150
|
+
|
151
|
+
it "uses ssl for https scheme regardless of port" do
|
152
|
+
request = HTTParty::Request.new(Net::HTTP::Get, 'https://foobar.com:123456')
|
153
|
+
request.send(:http).use_ssl?.should == true
|
154
|
+
end
|
155
|
+
|
156
|
+
context "PEM certificates" do
|
157
|
+
before do
|
158
|
+
OpenSSL::X509::Certificate.stub(:new)
|
159
|
+
OpenSSL::PKey::RSA.stub(:new)
|
160
|
+
end
|
161
|
+
|
162
|
+
context "when scheme is https" do
|
163
|
+
before do
|
164
|
+
@request.stub!(:uri).and_return(URI.parse("https://google.com"))
|
165
|
+
pem = :pem_contents
|
166
|
+
@cert = mock("OpenSSL::X509::Certificate")
|
167
|
+
@key = mock("OpenSSL::PKey::RSA")
|
168
|
+
OpenSSL::X509::Certificate.should_receive(:new).with(pem).and_return(@cert)
|
169
|
+
OpenSSL::PKey::RSA.should_receive(:new).with(pem, "password").and_return(@key)
|
170
|
+
|
171
|
+
@request.options[:pem] = pem
|
172
|
+
@request.options[:pem_password] = "password"
|
173
|
+
@pem_http = @request.send(:http)
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should use a PEM certificate when provided" do
|
177
|
+
@pem_http.cert.should == @cert
|
178
|
+
@pem_http.key.should == @key
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should verify the certificate when provided" do
|
182
|
+
@pem_http = @request.send(:http)
|
183
|
+
@pem_http.verify_mode.should == OpenSSL::SSL::VERIFY_PEER
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
context "when scheme is not https" do
|
188
|
+
it "does not assign a PEM" do
|
189
|
+
http = Net::HTTP.new('google.com')
|
190
|
+
http.should_not_receive(:cert=)
|
191
|
+
http.should_not_receive(:key=)
|
192
|
+
Net::HTTP.stub(:new => http)
|
193
|
+
|
194
|
+
request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com')
|
195
|
+
request.options[:pem] = :pem_contents
|
196
|
+
request.send(:http)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context "debugging" do
|
201
|
+
before do
|
202
|
+
@http = Net::HTTP.new('google.com')
|
203
|
+
Net::HTTP.stub(:new => @http)
|
204
|
+
@request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com')
|
205
|
+
end
|
206
|
+
|
207
|
+
it "calls #set_debug_output when the option is provided" do
|
208
|
+
@request.options[:debug_output] = $stderr
|
209
|
+
@http.should_receive(:set_debug_output).with($stderr)
|
210
|
+
@request.send(:http)
|
211
|
+
end
|
212
|
+
|
213
|
+
it "does not set_debug_output when the option is not provided" do
|
214
|
+
@http.should_not_receive(:set_debug_output)
|
215
|
+
@request.send(:http)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
context "when setting timeout" do
|
221
|
+
it "does nothing if the timeout option is a string" do
|
222
|
+
http = double("http").as_null_object
|
223
|
+
http.should_not_receive(:open_timeout=)
|
224
|
+
http.should_not_receive(:read_timeout=)
|
225
|
+
Net::HTTP.stub(:new => http)
|
226
|
+
|
227
|
+
request = HTTParty::Request.new(Net::HTTP::Get, 'https://foobar.com', {:timeout => "five seconds"})
|
228
|
+
request.send(:http)
|
229
|
+
end
|
230
|
+
|
231
|
+
it "sets the timeout to 5 seconds" do
|
232
|
+
@request.options[:timeout] = 5
|
233
|
+
@request.send(:http).open_timeout.should == 5
|
234
|
+
@request.send(:http).read_timeout.should == 5
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe '#format_from_mimetype' do
|
240
|
+
it 'should handle text/xml' do
|
241
|
+
["text/xml", "text/xml; charset=iso8859-1"].each do |ct|
|
242
|
+
@request.send(:format_from_mimetype, ct).should == :xml
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'should handle application/xml' do
|
247
|
+
["application/xml", "application/xml; charset=iso8859-1"].each do |ct|
|
248
|
+
@request.send(:format_from_mimetype, ct).should == :xml
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'should handle text/json' do
|
253
|
+
["text/json", "text/json; charset=iso8859-1"].each do |ct|
|
254
|
+
@request.send(:format_from_mimetype, ct).should == :json
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'should handle application/json' do
|
259
|
+
["application/json", "application/json; charset=iso8859-1"].each do |ct|
|
260
|
+
@request.send(:format_from_mimetype, ct).should == :json
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'should handle text/javascript' do
|
265
|
+
["text/javascript", "text/javascript; charset=iso8859-1"].each do |ct|
|
266
|
+
@request.send(:format_from_mimetype, ct).should == :json
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'should handle application/javascript' do
|
271
|
+
["application/javascript", "application/javascript; charset=iso8859-1"].each do |ct|
|
272
|
+
@request.send(:format_from_mimetype, ct).should == :json
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
it "returns nil for an unrecognized mimetype" do
|
277
|
+
@request.send(:format_from_mimetype, "application/atom+xml").should be_nil
|
278
|
+
end
|
279
|
+
|
280
|
+
it "returns nil when using a default parser" do
|
281
|
+
@request.options[:parser] = lambda {}
|
282
|
+
@request.send(:format_from_mimetype, "text/json").should be_nil
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
describe 'parsing responses' do
|
287
|
+
it 'should handle xml automatically' do
|
288
|
+
xml = %q[<books><book><id>1234</id><name>Foo Bar!</name></book></books>]
|
289
|
+
@request.options[:format] = :xml
|
290
|
+
@request.send(:parse_response, xml).should == {'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}}
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'should handle json automatically' do
|
294
|
+
json = %q[{"books": {"book": {"name": "Foo Bar!", "id": "1234"}}}]
|
295
|
+
@request.options[:format] = :json
|
296
|
+
@request.send(:parse_response, json).should == {'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}}
|
297
|
+
end
|
298
|
+
|
299
|
+
it 'should handle yaml automatically' do
|
300
|
+
yaml = "books: \n book: \n name: Foo Bar!\n id: \"1234\"\n"
|
301
|
+
@request.options[:format] = :yaml
|
302
|
+
@request.send(:parse_response, yaml).should == {'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}}
|
303
|
+
end
|
304
|
+
|
305
|
+
it "should include any HTTP headers in the returned response" do
|
306
|
+
@request.options[:format] = :html
|
307
|
+
response = stub_response "Content"
|
308
|
+
response.initialize_http_header("key" => "value")
|
309
|
+
|
310
|
+
@request.perform.headers.should == { "key" => ["value"] }
|
311
|
+
end
|
312
|
+
|
313
|
+
describe 'with non-200 responses' do
|
314
|
+
context "3xx responses" do
|
315
|
+
it 'returns a valid object for 304 not modified' do
|
316
|
+
stub_response '', 304
|
317
|
+
resp = @request.perform
|
318
|
+
resp.code.should == 304
|
319
|
+
resp.body.should == ''
|
320
|
+
resp.should be_nil
|
321
|
+
end
|
322
|
+
|
323
|
+
it "redirects if a 300 contains a location header" do
|
324
|
+
redirect = stub_response '', 300
|
325
|
+
redirect['location'] = 'http://foo.com/foo'
|
326
|
+
ok = stub_response('<hash><foo>bar</foo></hash>', 200)
|
327
|
+
@http.stub!(:request).and_return(redirect, ok)
|
328
|
+
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
329
|
+
end
|
330
|
+
|
331
|
+
it "redirects if a 300 contains a relative location header" do
|
332
|
+
redirect = stub_response '', 300
|
333
|
+
redirect['location'] = '/foo/bar'
|
334
|
+
ok = stub_response('<hash><foo>bar</foo></hash>', 200)
|
335
|
+
@http.stub!(:request).and_return(redirect, ok)
|
336
|
+
response = @request.perform
|
337
|
+
response.request.uri.request_uri.should == "/foo/bar"
|
338
|
+
response.should == {"hash" => {"foo" => "bar"}}
|
339
|
+
end
|
340
|
+
|
341
|
+
it "returns the HTTParty::Response when the 300 does not contain a location header" do
|
342
|
+
net_response = stub_response '', 300
|
343
|
+
HTTParty::Response.should === @request.perform
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
it 'should return a valid object for 4xx response' do
|
348
|
+
stub_response '<foo><bar>yes</bar></foo>', 401
|
349
|
+
resp = @request.perform
|
350
|
+
resp.code.should == 401
|
351
|
+
resp.body.should == "<foo><bar>yes</bar></foo>"
|
352
|
+
resp['foo']['bar'].should == "yes"
|
353
|
+
end
|
354
|
+
|
355
|
+
it 'should return a valid object for 5xx response' do
|
356
|
+
stub_response '<foo><bar>error</bar></foo>', 500
|
357
|
+
resp = @request.perform
|
358
|
+
resp.code.should == 500
|
359
|
+
resp.body.should == "<foo><bar>error</bar></foo>"
|
360
|
+
resp['foo']['bar'].should == "error"
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
it "should not attempt to parse empty responses" do
|
366
|
+
[204, 304].each do |code|
|
367
|
+
stub_response "", code
|
368
|
+
|
369
|
+
@request.options[:format] = :xml
|
370
|
+
@request.perform.should be_nil
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
it "should not fail for missing mime type" do
|
375
|
+
stub_response "Content for you"
|
376
|
+
@request.options[:format] = :html
|
377
|
+
@request.perform.should == 'Content for you'
|
378
|
+
end
|
379
|
+
|
380
|
+
describe "a request that redirects" do
|
381
|
+
before(:each) do
|
382
|
+
@redirect = stub_response("", 302)
|
383
|
+
@redirect['location'] = '/foo'
|
384
|
+
|
385
|
+
@ok = stub_response('<hash><foo>bar</foo></hash>', 200)
|
386
|
+
end
|
387
|
+
|
388
|
+
describe "once" do
|
389
|
+
before(:each) do
|
390
|
+
@http.stub!(:request).and_return(@redirect, @ok)
|
391
|
+
end
|
392
|
+
|
393
|
+
it "should be handled by GET transparently" do
|
394
|
+
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
395
|
+
end
|
396
|
+
|
397
|
+
it "should be handled by POST transparently" do
|
398
|
+
@request.http_method = Net::HTTP::Post
|
399
|
+
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
400
|
+
end
|
401
|
+
|
402
|
+
it "should be handled by DELETE transparently" do
|
403
|
+
@request.http_method = Net::HTTP::Delete
|
404
|
+
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
405
|
+
end
|
406
|
+
|
407
|
+
it "should be handled by PUT transparently" do
|
408
|
+
@request.http_method = Net::HTTP::Put
|
409
|
+
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
410
|
+
end
|
411
|
+
|
412
|
+
it "should be handled by HEAD transparently" do
|
413
|
+
@request.http_method = Net::HTTP::Head
|
414
|
+
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
415
|
+
end
|
416
|
+
|
417
|
+
it "should be handled by OPTIONS transparently" do
|
418
|
+
@request.http_method = Net::HTTP::Options
|
419
|
+
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
420
|
+
end
|
421
|
+
|
422
|
+
it "should keep track of cookies between redirects" do
|
423
|
+
@redirect['Set-Cookie'] = 'foo=bar; name=value; HTTPOnly'
|
424
|
+
@request.perform
|
425
|
+
@request.options[:headers]['Cookie'].should match(/foo=bar/)
|
426
|
+
@request.options[:headers]['Cookie'].should match(/name=value/)
|
427
|
+
end
|
428
|
+
|
429
|
+
it 'should update cookies with rediects' do
|
430
|
+
@request.options[:headers] = {'Cookie'=> 'foo=bar;'}
|
431
|
+
@redirect['Set-Cookie'] = 'foo=tar;'
|
432
|
+
@request.perform
|
433
|
+
@request.options[:headers]['Cookie'].should match(/foo=tar/)
|
434
|
+
end
|
435
|
+
|
436
|
+
it 'should keep cookies between rediects' do
|
437
|
+
@request.options[:headers] = {'Cookie'=> 'keep=me'}
|
438
|
+
@redirect['Set-Cookie'] = 'foo=tar;'
|
439
|
+
@request.perform
|
440
|
+
@request.options[:headers]['Cookie'].should match(/keep=me/)
|
441
|
+
end
|
442
|
+
|
443
|
+
it 'should make resulting request a get request if it not already' do
|
444
|
+
@request.http_method = Net::HTTP::Delete
|
445
|
+
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
446
|
+
@request.http_method.should == Net::HTTP::Get
|
447
|
+
end
|
448
|
+
|
449
|
+
it 'should not make resulting request a get request if options[:maintain_method_across_redirects] is true' do
|
450
|
+
@request.options[:maintain_method_across_redirects] = true
|
451
|
+
@request.http_method = Net::HTTP::Delete
|
452
|
+
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
453
|
+
@request.http_method.should == Net::HTTP::Delete
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
describe "infinitely" do
|
458
|
+
before(:each) do
|
459
|
+
@http.stub!(:request).and_return(@redirect)
|
460
|
+
end
|
461
|
+
|
462
|
+
it "should raise an exception" do
|
463
|
+
lambda { @request.perform }.should raise_error(HTTParty::RedirectionTooDeep)
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
context "with POST http method" do
|
469
|
+
it "should raise argument error if query is not a hash" do
|
470
|
+
lambda {
|
471
|
+
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :format => :xml, :query => 'astring').perform
|
472
|
+
}.should raise_error(ArgumentError)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
describe "argument validation" do
|
477
|
+
it "should raise argument error if basic_auth and digest_auth are both present" do
|
478
|
+
lambda {
|
479
|
+
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :basic_auth => {}, :digest_auth => {}).perform
|
480
|
+
}.should raise_error(ArgumentError, "only one authentication method, :basic_auth or :digest_auth may be used at a time")
|
481
|
+
end
|
482
|
+
|
483
|
+
it "should raise argument error if basic_auth is not a hash" do
|
484
|
+
lambda {
|
485
|
+
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :basic_auth => ["foo", "bar"]).perform
|
486
|
+
}.should raise_error(ArgumentError, ":basic_auth must be a hash")
|
487
|
+
end
|
488
|
+
|
489
|
+
it "should raise argument error if digest_auth is not a hash" do
|
490
|
+
lambda {
|
491
|
+
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :digest_auth => ["foo", "bar"]).perform
|
492
|
+
}.should raise_error(ArgumentError, ":digest_auth must be a hash")
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|