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.
Files changed (71) hide show
  1. data/.gitignore +9 -0
  2. data/Gemfile +6 -0
  3. data/History +253 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.rdoc +54 -0
  6. data/Rakefile +13 -0
  7. data/bin/httparty +108 -0
  8. data/cucumber.yml +1 -0
  9. data/examples/aaws.rb +32 -0
  10. data/examples/basic.rb +32 -0
  11. data/examples/custom_parsers.rb +67 -0
  12. data/examples/delicious.rb +37 -0
  13. data/examples/google.rb +16 -0
  14. data/examples/rubyurl.rb +14 -0
  15. data/examples/tripit_sign_in.rb +33 -0
  16. data/examples/twitter.rb +31 -0
  17. data/examples/whoismyrep.rb +10 -0
  18. data/features/basic_authentication.feature +20 -0
  19. data/features/command_line.feature +7 -0
  20. data/features/deals_with_http_error_codes.feature +26 -0
  21. data/features/digest_authentication.feature +20 -0
  22. data/features/handles_compressed_responses.feature +19 -0
  23. data/features/handles_multiple_formats.feature +34 -0
  24. data/features/steps/env.rb +22 -0
  25. data/features/steps/httparty_response_steps.rb +26 -0
  26. data/features/steps/httparty_steps.rb +27 -0
  27. data/features/steps/mongrel_helper.rb +94 -0
  28. data/features/steps/remote_service_steps.rb +69 -0
  29. data/features/supports_redirection.feature +22 -0
  30. data/features/supports_timeout_option.feature +13 -0
  31. data/httparty.gemspec +31 -0
  32. data/lib/httparty.rb +455 -0
  33. data/lib/httparty/cookie_hash.rb +22 -0
  34. data/lib/httparty/core_extensions.rb +9 -0
  35. data/lib/httparty/exceptions.rb +26 -0
  36. data/lib/httparty/module_inheritable_attributes.rb +34 -0
  37. data/lib/httparty/net_digest_auth.rb +71 -0
  38. data/lib/httparty/parser.rb +140 -0
  39. data/lib/httparty/request.rb +252 -0
  40. data/lib/httparty/response.rb +85 -0
  41. data/lib/httparty/version.rb +3 -0
  42. data/spec/fixtures/delicious.xml +23 -0
  43. data/spec/fixtures/empty.xml +0 -0
  44. data/spec/fixtures/google.html +3 -0
  45. data/spec/fixtures/ssl/generate.sh +29 -0
  46. data/spec/fixtures/ssl/generated/1fe462c2.0 +16 -0
  47. data/spec/fixtures/ssl/generated/bogushost.crt +13 -0
  48. data/spec/fixtures/ssl/generated/ca.crt +16 -0
  49. data/spec/fixtures/ssl/generated/ca.key +15 -0
  50. data/spec/fixtures/ssl/generated/selfsigned.crt +14 -0
  51. data/spec/fixtures/ssl/generated/server.crt +13 -0
  52. data/spec/fixtures/ssl/generated/server.key +15 -0
  53. data/spec/fixtures/ssl/openssl-exts.cnf +9 -0
  54. data/spec/fixtures/twitter.json +1 -0
  55. data/spec/fixtures/twitter.xml +403 -0
  56. data/spec/fixtures/undefined_method_add_node_for_nil.xml +2 -0
  57. data/spec/httparty/cookie_hash_spec.rb +71 -0
  58. data/spec/httparty/net_digest_auth_spec.rb +93 -0
  59. data/spec/httparty/parser_spec.rb +155 -0
  60. data/spec/httparty/request_spec.rb +496 -0
  61. data/spec/httparty/response_spec.rb +193 -0
  62. data/spec/httparty/ssl_spec.rb +54 -0
  63. data/spec/httparty_spec.rb +621 -0
  64. data/spec/spec.opts +3 -0
  65. data/spec/spec_helper.rb +23 -0
  66. data/spec/support/ssl_test_helper.rb +25 -0
  67. data/spec/support/ssl_test_server.rb +69 -0
  68. data/spec/support/stub_response.rb +30 -0
  69. data/website/css/common.css +47 -0
  70. data/website/index.html +73 -0
  71. metadata +206 -0
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <Entities total="0" results="0" page="1" page-size="25" href="https://s3-sandbox.parature.com/api/v1/5578/5633/Account" />
@@ -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
+