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