bartzon-httparty 0.6.1

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 (70) hide show
  1. data/.gitignore +8 -0
  2. data/History +216 -0
  3. data/MIT-LICENSE +20 -0
  4. data/Manifest +47 -0
  5. data/README.rdoc +54 -0
  6. data/Rakefile +89 -0
  7. data/VERSION +1 -0
  8. data/bartzon-httparty.gemspec +147 -0
  9. data/bin/httparty +108 -0
  10. data/cucumber.yml +1 -0
  11. data/examples/aaws.rb +32 -0
  12. data/examples/basic.rb +11 -0
  13. data/examples/custom_parsers.rb +67 -0
  14. data/examples/delicious.rb +37 -0
  15. data/examples/google.rb +16 -0
  16. data/examples/rubyurl.rb +14 -0
  17. data/examples/twitter.rb +31 -0
  18. data/examples/whoismyrep.rb +10 -0
  19. data/features/basic_authentication.feature +20 -0
  20. data/features/command_line.feature +7 -0
  21. data/features/deals_with_http_error_codes.feature +26 -0
  22. data/features/digest_authentication.feature +20 -0
  23. data/features/handles_compressed_responses.feature +19 -0
  24. data/features/handles_multiple_formats.feature +34 -0
  25. data/features/steps/env.rb +23 -0
  26. data/features/steps/httparty_response_steps.rb +26 -0
  27. data/features/steps/httparty_steps.rb +27 -0
  28. data/features/steps/mongrel_helper.rb +94 -0
  29. data/features/steps/remote_service_steps.rb +69 -0
  30. data/features/supports_redirection.feature +22 -0
  31. data/features/supports_timeout_option.feature +13 -0
  32. data/httparty.gemspec +146 -0
  33. data/lib/httparty.rb +383 -0
  34. data/lib/httparty/cookie_hash.rb +22 -0
  35. data/lib/httparty/core_extensions.rb +31 -0
  36. data/lib/httparty/exceptions.rb +26 -0
  37. data/lib/httparty/module_inheritable_attributes.rb +34 -0
  38. data/lib/httparty/net_digest_auth.rb +35 -0
  39. data/lib/httparty/parser.rb +141 -0
  40. data/lib/httparty/request.rb +277 -0
  41. data/lib/httparty/response.rb +79 -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 +15 -0
  47. data/spec/fixtures/ssl/generated/bogushost.crt +13 -0
  48. data/spec/fixtures/ssl/generated/ca.crt +15 -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/parser_spec.rb +155 -0
  59. data/spec/httparty/request_spec.rb +488 -0
  60. data/spec/httparty/response_spec.rb +188 -0
  61. data/spec/httparty/ssl_spec.rb +55 -0
  62. data/spec/httparty_spec.rb +570 -0
  63. data/spec/spec.opts +3 -0
  64. data/spec/spec_helper.rb +20 -0
  65. data/spec/support/ssl_test_helper.rb +25 -0
  66. data/spec/support/ssl_test_server.rb +69 -0
  67. data/spec/support/stub_response.rb +30 -0
  68. data/website/css/common.css +47 -0
  69. data/website/index.html +73 -0
  70. metadata +244 -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 File.expand_path(File.join(File.dirname(__FILE__), '../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,155 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '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 Crack" do
133
+ Crack::XML.should_receive(:parse).with('body')
134
+ subject.send(:xml)
135
+ end
136
+
137
+ it "parses json with Crack" do
138
+ Crack::JSON.should_receive(:parse).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,488 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ module RequestStubbable
4
+ def stub_response(body, code = 200)
5
+ unless @http
6
+ @http = Net::HTTP.new('localhost', 80)
7
+ @request.stub!(:http).and_return(@http)
8
+ @request.stub!(:uri).and_return(URI.parse("http://foo.com/foobar"))
9
+ end
10
+
11
+ response = Net::HTTPResponse::CODE_TO_OBJ[code.to_s].new("1.1", code, body)
12
+ response.stub!(:body).and_return(body)
13
+
14
+ @http.stub!(:request).and_return(response)
15
+ response
16
+ end
17
+ end
18
+
19
+ describe HTTParty::Request do
20
+ include RequestStubbable
21
+
22
+ before do
23
+ @request = HTTParty::Request.new(Net::HTTP::Get, 'http://api.foo.com/v1', :format => :xml)
24
+ end
25
+
26
+ describe "initialization" do
27
+ it "sets parser to HTTParty::Parser" do
28
+ request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com')
29
+ request.parser.should == HTTParty::Parser
30
+ end
31
+
32
+ it "sets parser to the optional parser" do
33
+ my_parser = lambda {}
34
+ request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com', :parser => my_parser)
35
+ request.parser.should == my_parser
36
+ end
37
+ end
38
+
39
+ describe "#format" do
40
+ context "request yet to be made" do
41
+ it "returns format option" do
42
+ request = HTTParty::Request.new 'get', '/', :format => :xml
43
+ request.format.should == :xml
44
+ end
45
+
46
+ it "returns nil format" do
47
+ request = HTTParty::Request.new 'get', '/'
48
+ request.format.should be_nil
49
+ end
50
+ end
51
+
52
+ context "request has been made" do
53
+ it "returns format option" do
54
+ request = HTTParty::Request.new 'get', '/', :format => :xml
55
+ request.last_response = stub
56
+ request.format.should == :xml
57
+ end
58
+
59
+ it "returns the content-type from the last response when the option is not set" do
60
+ request = HTTParty::Request.new 'get', '/'
61
+ response = stub
62
+ response.should_receive(:[]).with('content-type').and_return('text/json')
63
+ request.last_response = response
64
+ request.format.should == :json
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ context "options" do
71
+ it "should use basic auth when configured" do
72
+ @request.options[:basic_auth] = {:username => 'foobar', :password => 'secret'}
73
+ @request.send(:setup_raw_request)
74
+ @request.instance_variable_get(:@raw_request)['authorization'].should_not be_nil
75
+ end
76
+
77
+ it "should use digest auth when configured" do
78
+ FakeWeb.register_uri(:head, "http://api.foo.com/v1",
79
+ :www_authenticate => 'Digest realm="Log Viewer", qop="auth", nonce="2CA0EC6B0E126C4800E56BA0C0003D3C", opaque="5ccc069c403ebaf9f0171e9517f40e41", stale=false')
80
+
81
+ @request.options[:digest_auth] = {:username => 'foobar', :password => 'secret'}
82
+ @request.send(:setup_raw_request)
83
+
84
+ raw_request = @request.instance_variable_get(:@raw_request)
85
+ raw_request.instance_variable_get(:@header)['Authorization'].should_not be_nil
86
+ end
87
+ end
88
+
89
+ describe "#uri" do
90
+ context "query strings" do
91
+ it "does not add an empty query string when default_params are blank" do
92
+ @request.options[:default_params] = {}
93
+ @request.uri.query.should be_nil
94
+ end
95
+ end
96
+ end
97
+
98
+ describe 'http' do
99
+ it "should use ssl for port 443" do
100
+ request = HTTParty::Request.new(Net::HTTP::Get, 'https://api.foo.com/v1:443')
101
+ request.send(:http).use_ssl?.should == true
102
+ end
103
+
104
+ it 'should not use ssl for port 80' do
105
+ request = HTTParty::Request.new(Net::HTTP::Get, 'http://foobar.com')
106
+ request.send(:http).use_ssl?.should == false
107
+ end
108
+
109
+ it "uses ssl for https scheme with default port" do
110
+ request = HTTParty::Request.new(Net::HTTP::Get, 'https://foobar.com')
111
+ request.send(:http).use_ssl?.should == true
112
+ end
113
+
114
+ it "uses ssl for https scheme regardless of port" do
115
+ request = HTTParty::Request.new(Net::HTTP::Get, 'https://foobar.com:123456')
116
+ request.send(:http).use_ssl?.should == true
117
+ end
118
+
119
+ context "PEM certificates" do
120
+ before do
121
+ OpenSSL::X509::Certificate.stub(:new)
122
+ OpenSSL::PKey::RSA.stub(:new)
123
+ end
124
+
125
+ context "when scheme is https" do
126
+ before do
127
+ @request.stub!(:uri).and_return(URI.parse("https://google.com"))
128
+ pem = :pem_contents
129
+ @cert = mock("OpenSSL::X509::Certificate")
130
+ @key = mock("OpenSSL::PKey::RSA")
131
+ OpenSSL::X509::Certificate.should_receive(:new).with(pem).and_return(@cert)
132
+ OpenSSL::PKey::RSA.should_receive(:new).with(pem).and_return(@key)
133
+
134
+ @request.options[:pem] = pem
135
+ @pem_http = @request.send(:http)
136
+ end
137
+
138
+ it "should use a PEM certificate when provided" do
139
+ @pem_http.cert.should == @cert
140
+ @pem_http.key.should == @key
141
+ end
142
+
143
+ it "should verify the certificate when provided" do
144
+ @pem_http = @request.send(:http)
145
+ @pem_http.verify_mode.should == OpenSSL::SSL::VERIFY_PEER
146
+ end
147
+ end
148
+
149
+ context "when scheme is not https" do
150
+ it "does not assign a PEM" do
151
+ http = Net::HTTP.new('google.com')
152
+ http.should_not_receive(:cert=)
153
+ http.should_not_receive(:key=)
154
+ Net::HTTP.stub(:new => http)
155
+
156
+ request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com')
157
+ request.options[:pem] = :pem_contents
158
+ request.send(:http)
159
+ end
160
+ end
161
+
162
+ context "debugging" do
163
+ before do
164
+ @http = Net::HTTP.new('google.com')
165
+ Net::HTTP.stub(:new => @http)
166
+ @request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com')
167
+ end
168
+
169
+ it "calls #set_debug_output when the option is provided" do
170
+ @request.options[:debug_output] = $stderr
171
+ @http.should_receive(:set_debug_output).with($stderr)
172
+ @request.send(:http)
173
+ end
174
+
175
+ it "does not set_debug_output when the option is not provided" do
176
+ @http.should_not_receive(:set_debug_output)
177
+ @request.send(:http)
178
+ end
179
+ end
180
+ end
181
+
182
+ context "when setting timeout" do
183
+ it "does nothing if the timeout option is a string" do
184
+ http = mock("http", :null_object => true)
185
+ http.should_not_receive(:open_timeout=)
186
+ http.should_not_receive(:read_timeout=)
187
+ Net::HTTP.stub(:new => http)
188
+
189
+ request = HTTParty::Request.new(Net::HTTP::Get, 'https://foobar.com', {:timeout => "five seconds"})
190
+ request.send(:http)
191
+ end
192
+
193
+ it "sets the timeout to 5 seconds" do
194
+ @request.options[:timeout] = 5
195
+ @request.send(:http).open_timeout.should == 5
196
+ @request.send(:http).read_timeout.should == 5
197
+ end
198
+ end
199
+ end
200
+
201
+ describe '#format_from_mimetype' do
202
+ it 'should handle text/xml' do
203
+ ["text/xml", "text/xml; charset=iso8859-1"].each do |ct|
204
+ @request.send(:format_from_mimetype, ct).should == :xml
205
+ end
206
+ end
207
+
208
+ it 'should handle application/xml' do
209
+ ["application/xml", "application/xml; charset=iso8859-1"].each do |ct|
210
+ @request.send(:format_from_mimetype, ct).should == :xml
211
+ end
212
+ end
213
+
214
+ it 'should handle text/json' do
215
+ ["text/json", "text/json; charset=iso8859-1"].each do |ct|
216
+ @request.send(:format_from_mimetype, ct).should == :json
217
+ end
218
+ end
219
+
220
+ it 'should handle application/json' do
221
+ ["application/json", "application/json; charset=iso8859-1"].each do |ct|
222
+ @request.send(:format_from_mimetype, ct).should == :json
223
+ end
224
+ end
225
+
226
+ it 'should handle text/javascript' do
227
+ ["text/javascript", "text/javascript; charset=iso8859-1"].each do |ct|
228
+ @request.send(:format_from_mimetype, ct).should == :json
229
+ end
230
+ end
231
+
232
+ it 'should handle application/javascript' do
233
+ ["application/javascript", "application/javascript; charset=iso8859-1"].each do |ct|
234
+ @request.send(:format_from_mimetype, ct).should == :json
235
+ end
236
+ end
237
+
238
+ it "returns nil for an unrecognized mimetype" do
239
+ @request.send(:format_from_mimetype, "application/atom+xml").should be_nil
240
+ end
241
+
242
+ it "returns nil when using a default parser" do
243
+ @request.options[:parser] = lambda {}
244
+ @request.send(:format_from_mimetype, "text/json").should be_nil
245
+ end
246
+ end
247
+
248
+ describe 'parsing responses' do
249
+ it 'should handle xml automatically' do
250
+ xml = %q[<books><book><id>1234</id><name>Foo Bar!</name></book></books>]
251
+ @request.options[:format] = :xml
252
+ @request.send(:parse_response, xml).should == {'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}}
253
+ end
254
+
255
+ it 'should handle json automatically' do
256
+ json = %q[{"books": {"book": {"name": "Foo Bar!", "id": "1234"}}}]
257
+ @request.options[:format] = :json
258
+ @request.send(:parse_response, json).should == {'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}}
259
+ end
260
+
261
+ it 'should handle yaml automatically' do
262
+ yaml = "books: \n book: \n name: Foo Bar!\n id: \"1234\"\n"
263
+ @request.options[:format] = :yaml
264
+ @request.send(:parse_response, yaml).should == {'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}}
265
+ end
266
+
267
+ it "should include any HTTP headers in the returned response" do
268
+ @request.options[:format] = :html
269
+ response = stub_response "Content"
270
+ response.initialize_http_header("key" => "value")
271
+
272
+ @request.perform.headers.should == { "key" => ["value"] }
273
+ end
274
+
275
+ describe 'with non-200 responses' do
276
+ context "3xx responses" do
277
+ it 'returns a valid object for 304 not modified' do
278
+ stub_response '', 304
279
+ resp = @request.perform
280
+ resp.code.should == 304
281
+ resp.body.should == ''
282
+ resp.should be_nil
283
+ end
284
+
285
+ it "redirects if a 300 contains a location header" do
286
+ redirect = stub_response '', 300
287
+ redirect['location'] = 'http://foo.com/foo'
288
+ ok = stub_response('<hash><foo>bar</foo></hash>', 200)
289
+ @http.stub!(:request).and_return(redirect, ok)
290
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
291
+ end
292
+
293
+ it "returns the Net::HTTP response if the 300 does not contain a location header" do
294
+ net_response = stub_response '', 300
295
+ @request.perform.should be_kind_of(Net::HTTPMultipleChoice)
296
+ end
297
+ end
298
+
299
+ it 'should return a valid object for 4xx response' do
300
+ stub_response '<foo><bar>yes</bar></foo>', 401
301
+ resp = @request.perform
302
+ resp.code.should == 401
303
+ resp.body.should == "<foo><bar>yes</bar></foo>"
304
+ resp['foo']['bar'].should == "yes"
305
+ end
306
+
307
+ it 'should return a valid object for 5xx response' do
308
+ stub_response '<foo><bar>error</bar></foo>', 500
309
+ resp = @request.perform
310
+ resp.code.should == 500
311
+ resp.body.should == "<foo><bar>error</bar></foo>"
312
+ resp['foo']['bar'].should == "error"
313
+ end
314
+ end
315
+ end
316
+
317
+ it "should not attempt to parse empty responses" do
318
+ [204, 304].each do |code|
319
+ stub_response "", code
320
+
321
+ @request.options[:format] = :xml
322
+ @request.perform.should be_nil
323
+ end
324
+ end
325
+
326
+ it "should not fail for missing mime type" do
327
+ stub_response "Content for you"
328
+ @request.options[:format] = :html
329
+ @request.perform.should == 'Content for you'
330
+ end
331
+
332
+ describe "a request that redirects" do
333
+ before(:each) do
334
+ @redirect = stub_response("", 302)
335
+ @redirect['location'] = '/foo'
336
+
337
+ @ok = stub_response('<hash><foo>bar</foo></hash>', 200)
338
+ end
339
+
340
+ describe "once" do
341
+ before(:each) do
342
+ @http.stub!(:request).and_return(@redirect, @ok)
343
+ end
344
+
345
+ it "should be handled by GET transparently" do
346
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
347
+ end
348
+
349
+ it "should be handled by POST transparently" do
350
+ @request.http_method = Net::HTTP::Post
351
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
352
+ end
353
+
354
+ it "should be handled by DELETE transparently" do
355
+ @request.http_method = Net::HTTP::Delete
356
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
357
+ end
358
+
359
+ it "should be handled by PUT transparently" do
360
+ @request.http_method = Net::HTTP::Put
361
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
362
+ end
363
+
364
+ it "should be handled by HEAD transparently" do
365
+ @request.http_method = Net::HTTP::Head
366
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
367
+ end
368
+
369
+ it "should be handled by OPTIONS transparently" do
370
+ @request.http_method = Net::HTTP::Options
371
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
372
+ end
373
+
374
+ it "should keep track of cookies between redirects" do
375
+ @redirect['Set-Cookie'] = 'foo=bar; name=value; HTTPOnly'
376
+ @request.perform
377
+ @request.options[:headers]['Cookie'].should match(/foo=bar/)
378
+ @request.options[:headers]['Cookie'].should match(/name=value/)
379
+ end
380
+
381
+ it 'should update cookies with rediects' do
382
+ @request.options[:headers] = {'Cookie'=> 'foo=bar;'}
383
+ @redirect['Set-Cookie'] = 'foo=tar;'
384
+ @request.perform
385
+ @request.options[:headers]['Cookie'].should match(/foo=tar/)
386
+ end
387
+
388
+ it 'should keep cookies between rediects' do
389
+ @request.options[:headers] = {'Cookie'=> 'keep=me'}
390
+ @redirect['Set-Cookie'] = 'foo=tar;'
391
+ @request.perform
392
+ @request.options[:headers]['Cookie'].should match(/keep=me/)
393
+ end
394
+
395
+ it 'should make resulting request a get request if it not already' do
396
+ @request.http_method = Net::HTTP::Delete
397
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
398
+ @request.http_method.should == Net::HTTP::Get
399
+ end
400
+
401
+ it 'should not make resulting request a get request if options[:maintain_method_across_redirects] is true' do
402
+ @request.options[:maintain_method_across_redirects] = true
403
+ @request.http_method = Net::HTTP::Delete
404
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
405
+ @request.http_method.should == Net::HTTP::Delete
406
+ end
407
+ end
408
+
409
+ describe "infinitely" do
410
+ before(:each) do
411
+ @http.stub!(:request).and_return(@redirect)
412
+ end
413
+
414
+ it "should raise an exception" do
415
+ lambda { @request.perform }.should raise_error(HTTParty::RedirectionTooDeep)
416
+ end
417
+ end
418
+ end
419
+
420
+ context "with POST http method" do
421
+ it "should raise argument error if query is not a hash" do
422
+ lambda {
423
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :format => :xml, :query => 'astring').perform
424
+ }.should raise_error(ArgumentError)
425
+ end
426
+ end
427
+
428
+ describe "argument validation" do
429
+ it "should raise argument error if basic_auth and digest_auth are both present" do
430
+ lambda {
431
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :basic_auth => {}, :digest_auth => {}).perform
432
+ }.should raise_error(ArgumentError, "only one authentication method, :basic_auth or :digest_auth may be used at a time")
433
+ end
434
+
435
+ it "should raise argument error if basic_auth is not a hash" do
436
+ lambda {
437
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :basic_auth => ["foo", "bar"]).perform
438
+ }.should raise_error(ArgumentError, ":basic_auth must be a hash")
439
+ end
440
+
441
+ it "should raise argument error if digest_auth is not a hash" do
442
+ lambda {
443
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :digest_auth => ["foo", "bar"]).perform
444
+ }.should raise_error(ArgumentError, ":digest_auth must be a hash")
445
+ end
446
+ end
447
+
448
+ describe HTTParty::Request, "with multipart POST http method" do
449
+ TEMP_FILE = "tmp_foo.txt"
450
+
451
+ def create_temp_file!
452
+ File.open(TEMP_FILE, "w") { |fp| fp << "testjlkdsjfkldkfds" }
453
+ end
454
+
455
+ def remove_temp_file!
456
+ File.delete(TEMP_FILE)
457
+ end
458
+
459
+ it "should require at least one file to be attached" do
460
+ lambda {
461
+ request = HTTParty::Request.new(Net::HTTP::Post::Multipart,
462
+ 'http://api.foo.com/v1',
463
+ :multipart => {})
464
+ request.perform
465
+ }.should raise_error(ArgumentError)
466
+ end
467
+
468
+ it "should allow multipart as a valid option" do
469
+ stub_response "Foo"
470
+
471
+ lambda {
472
+ create_temp_file!
473
+ request = HTTParty::Request.new(Net::HTTP::Post::Multipart,
474
+ 'http://api.foo.com/v1',
475
+ :multipart => {
476
+ 'file' => {
477
+ :path => TEMP_FILE,
478
+ :type => 'text/plain'
479
+ }
480
+ })
481
+
482
+ request.perform
483
+ remove_temp_file!
484
+ }.should_not raise_error
485
+ end
486
+ end
487
+ end
488
+