httsoiree 0.13.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 (94) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +14 -0
  5. data/Guardfile +16 -0
  6. data/History +303 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +77 -0
  9. data/Rakefile +12 -0
  10. data/bin/httparty +117 -0
  11. data/cucumber.yml +1 -0
  12. data/examples/aaws.rb +32 -0
  13. data/examples/basic.rb +28 -0
  14. data/examples/crack.rb +19 -0
  15. data/examples/custom_parsers.rb +67 -0
  16. data/examples/delicious.rb +37 -0
  17. data/examples/google.rb +16 -0
  18. data/examples/headers_and_user_agents.rb +6 -0
  19. data/examples/logging.rb +38 -0
  20. data/examples/nokogiri_html_parser.rb +22 -0
  21. data/examples/rubyurl.rb +14 -0
  22. data/examples/stackexchange.rb +24 -0
  23. data/examples/tripit_sign_in.rb +33 -0
  24. data/examples/twitter.rb +31 -0
  25. data/examples/whoismyrep.rb +10 -0
  26. data/features/basic_authentication.feature +20 -0
  27. data/features/command_line.feature +7 -0
  28. data/features/deals_with_http_error_codes.feature +26 -0
  29. data/features/digest_authentication.feature +20 -0
  30. data/features/handles_compressed_responses.feature +27 -0
  31. data/features/handles_multiple_formats.feature +57 -0
  32. data/features/steps/env.rb +22 -0
  33. data/features/steps/httparty_response_steps.rb +52 -0
  34. data/features/steps/httparty_steps.rb +43 -0
  35. data/features/steps/mongrel_helper.rb +94 -0
  36. data/features/steps/remote_service_steps.rb +74 -0
  37. data/features/supports_read_timeout_option.feature +13 -0
  38. data/features/supports_redirection.feature +22 -0
  39. data/features/supports_timeout_option.feature +13 -0
  40. data/httparty.gemspec +25 -0
  41. data/lib/httparty/connection_adapter.rb +188 -0
  42. data/lib/httparty/cookie_hash.rb +22 -0
  43. data/lib/httparty/core_extensions.rb +32 -0
  44. data/lib/httparty/exceptions.rb +29 -0
  45. data/lib/httparty/hash_conversions.rb +51 -0
  46. data/lib/httparty/logger/apache_logger.rb +22 -0
  47. data/lib/httparty/logger/curl_logger.rb +48 -0
  48. data/lib/httparty/logger/logger.rb +18 -0
  49. data/lib/httparty/module_inheritable_attributes.rb +56 -0
  50. data/lib/httparty/net_digest_auth.rb +84 -0
  51. data/lib/httparty/parser.rb +141 -0
  52. data/lib/httparty/request.rb +339 -0
  53. data/lib/httparty/response/headers.rb +31 -0
  54. data/lib/httparty/response.rb +72 -0
  55. data/lib/httparty/version.rb +3 -0
  56. data/lib/httparty.rb +618 -0
  57. data/lib/httsoiree.rb +3 -0
  58. data/script/release +42 -0
  59. data/spec/fixtures/delicious.xml +23 -0
  60. data/spec/fixtures/empty.xml +0 -0
  61. data/spec/fixtures/google.html +3 -0
  62. data/spec/fixtures/ssl/generate.sh +29 -0
  63. data/spec/fixtures/ssl/generated/1fe462c2.0 +16 -0
  64. data/spec/fixtures/ssl/generated/bogushost.crt +13 -0
  65. data/spec/fixtures/ssl/generated/ca.crt +16 -0
  66. data/spec/fixtures/ssl/generated/ca.key +15 -0
  67. data/spec/fixtures/ssl/generated/selfsigned.crt +14 -0
  68. data/spec/fixtures/ssl/generated/server.crt +13 -0
  69. data/spec/fixtures/ssl/generated/server.key +15 -0
  70. data/spec/fixtures/ssl/openssl-exts.cnf +9 -0
  71. data/spec/fixtures/twitter.csv +2 -0
  72. data/spec/fixtures/twitter.json +1 -0
  73. data/spec/fixtures/twitter.xml +403 -0
  74. data/spec/fixtures/undefined_method_add_node_for_nil.xml +2 -0
  75. data/spec/httparty/connection_adapter_spec.rb +370 -0
  76. data/spec/httparty/cookie_hash_spec.rb +83 -0
  77. data/spec/httparty/exception_spec.rb +23 -0
  78. data/spec/httparty/logger/apache_logger_spec.rb +41 -0
  79. data/spec/httparty/logger/curl_logger_spec.rb +18 -0
  80. data/spec/httparty/logger/logger_spec.rb +22 -0
  81. data/spec/httparty/net_digest_auth_spec.rb +152 -0
  82. data/spec/httparty/parser_spec.rb +165 -0
  83. data/spec/httparty/request_spec.rb +774 -0
  84. data/spec/httparty/response_spec.rb +221 -0
  85. data/spec/httparty/ssl_spec.rb +74 -0
  86. data/spec/httparty_spec.rb +783 -0
  87. data/spec/spec.opts +2 -0
  88. data/spec/spec_helper.rb +37 -0
  89. data/spec/support/ssl_test_helper.rb +47 -0
  90. data/spec/support/ssl_test_server.rb +80 -0
  91. data/spec/support/stub_response.rb +43 -0
  92. data/website/css/common.css +47 -0
  93. data/website/index.html +73 -0
  94. metadata +215 -0
@@ -0,0 +1,774 @@
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 "::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
+
18
+ it "doesn't include brackets" do
19
+ query_string = normalizer[{page: 1, foo: %w(bar baz)}]
20
+ URI.unescape(query_string).should == "foo=bar&foo=baz&page=1"
21
+ end
22
+
23
+ it "URI encodes array values" do
24
+ query_string = normalizer[{people: ["Otis Redding", "Bob Marley", "Tim & Jon"], page: 1, xyzzy: 3}]
25
+ query_string.should == "page=1&people=Otis%20Redding&people=Bob%20Marley&people=Tim%20%26%20Jon&xyzzy=3"
26
+ end
27
+ end
28
+
29
+ context "when the query is a hash" do
30
+ it "correctly handles nil values" do
31
+ query_string = normalizer[{page: 1, per_page: nil}]
32
+ query_string.should == "page=1&per_page"
33
+ end
34
+ end
35
+ end
36
+
37
+ describe "initialization" do
38
+ it "sets parser to HTTParty::Parser" do
39
+ request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com')
40
+ request.parser.should == HTTParty::Parser
41
+ end
42
+
43
+ it "sets parser to the optional parser" do
44
+ my_parser = lambda {}
45
+ request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com', parser: my_parser)
46
+ request.parser.should == my_parser
47
+ end
48
+
49
+ it "sets connection_adapter to HTTPParty::ConnectionAdapter" do
50
+ request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com')
51
+ request.connection_adapter.should == HTTParty::ConnectionAdapter
52
+ end
53
+
54
+ it "sets connection_adapter to the optional connection_adapter" do
55
+ my_adapter = lambda {}
56
+ request = HTTParty::Request.new(Net::HTTP::Get, 'http://google.com', connection_adapter: my_adapter)
57
+ request.connection_adapter.should == my_adapter
58
+ end
59
+ end
60
+
61
+ describe "#format" do
62
+ context "request yet to be made" do
63
+ it "returns format option" do
64
+ request = HTTParty::Request.new 'get', '/', format: :xml
65
+ request.format.should == :xml
66
+ end
67
+
68
+ it "returns nil format" do
69
+ request = HTTParty::Request.new 'get', '/'
70
+ request.format.should be_nil
71
+ end
72
+ end
73
+
74
+ context "request has been made" do
75
+ it "returns format option" do
76
+ request = HTTParty::Request.new 'get', '/', format: :xml
77
+ request.last_response = stub
78
+ request.format.should == :xml
79
+ end
80
+
81
+ it "returns the content-type from the last response when the option is not set" do
82
+ request = HTTParty::Request.new 'get', '/'
83
+ response = stub
84
+ response.should_receive(:[]).with('content-type').and_return('text/json')
85
+ request.last_response = response
86
+ request.format.should == :json
87
+ end
88
+ end
89
+
90
+ end
91
+
92
+ context "options" do
93
+ it "should use basic auth when configured" do
94
+ @request.options[:basic_auth] = {username: 'foobar', password: 'secret'}
95
+ @request.send(:setup_raw_request)
96
+ @request.instance_variable_get(:@raw_request)['authorization'].should_not be_nil
97
+ end
98
+
99
+ it "should use digest auth when configured" do
100
+ FakeWeb.register_uri(:get, "http://api.foo.com/v1",
101
+ www_authenticate: 'Digest realm="Log Viewer", qop="auth", nonce="2CA0EC6B0E126C4800E56BA0C0003D3C", opaque="5ccc069c403ebaf9f0171e9517f40e41", stale=false')
102
+
103
+ @request.options[:digest_auth] = {username: 'foobar', password: 'secret'}
104
+ @request.send(:setup_raw_request)
105
+
106
+ raw_request = @request.instance_variable_get(:@raw_request)
107
+ raw_request.instance_variable_get(:@header)['Authorization'].should_not be_nil
108
+ end
109
+
110
+ it "should use the right http method for digest authentication" do
111
+ @post_request = HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', format: :xml)
112
+ FakeWeb.register_uri(:post, "http://api.foo.com/v1", {})
113
+
114
+ http = @post_request.send(:http)
115
+ @post_request.should_receive(:http).and_return(http)
116
+ http.should_not_receive(:head).and_return({'www-authenticate' => nil})
117
+ @post_request.options[:digest_auth] = {username: 'foobar', password: 'secret'}
118
+ @post_request.send(:setup_raw_request)
119
+ end
120
+
121
+ it 'should use body_stream when configured' do
122
+ stream = StringIO.new('foo')
123
+ request = HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', body_stream: stream)
124
+ request.send(:setup_raw_request)
125
+ request.instance_variable_get(:@raw_request).body_stream.should == stream
126
+ end
127
+ end
128
+
129
+ describe "#uri" do
130
+ context "query strings" do
131
+ it "does not add an empty query string when default_params are blank" do
132
+ @request.options[:default_params] = {}
133
+ @request.uri.query.should be_nil
134
+ end
135
+
136
+ it "respects the query string normalization proc" do
137
+ empty_proc = lambda {|qs| ""}
138
+ @request.options[:query_string_normalizer] = empty_proc
139
+ @request.options[:query] = {foo: :bar}
140
+ URI.unescape(@request.uri.query).should == ""
141
+ end
142
+
143
+ it "does not append an ampersand when queries are embedded in paths" do
144
+ @request.path = "/path?a=1"
145
+ @request.options[:query] = {}
146
+ @request.uri.query.should == "a=1"
147
+ end
148
+
149
+ it "does not duplicate query string parameters when uri is called twice" do
150
+ @request.options[:query] = {foo: :bar}
151
+ @request.uri
152
+ @request.uri.query.should == "foo=bar"
153
+ end
154
+
155
+ context "when representing an array" do
156
+ it "returns a Rails style query string" do
157
+ @request.options[:query] = {foo: %w(bar baz)}
158
+ URI.unescape(@request.uri.query).should == "foo[]=bar&foo[]=baz"
159
+ end
160
+ end
161
+
162
+ end
163
+ end
164
+
165
+ describe "#setup_raw_request" do
166
+ context "when query_string_normalizer is set" do
167
+ it "sets the body to the return value of the proc" do
168
+ @request.options[:query_string_normalizer] = HTTParty::Request::NON_RAILS_QUERY_STRING_NORMALIZER
169
+ @request.options[:body] = {page: 1, foo: %w(bar baz)}
170
+ @request.send(:setup_raw_request)
171
+ body = @request.instance_variable_get(:@raw_request).body
172
+ URI.unescape(body).should == "foo=bar&foo=baz&page=1"
173
+ end
174
+ end
175
+ end
176
+
177
+ describe 'http' do
178
+ it "should get a connection from the connection_adapter" do
179
+ http = Net::HTTP.new('google.com')
180
+ adapter = mock('adapter')
181
+ request = HTTParty::Request.new(Net::HTTP::Get, 'https://api.foo.com/v1:443', connection_adapter: adapter)
182
+ adapter.should_receive(:call).with(request.uri, request.options).and_return(http)
183
+ request.send(:http).should be http
184
+ end
185
+ end
186
+
187
+ describe '#format_from_mimetype' do
188
+ it 'should handle text/xml' do
189
+ ["text/xml", "text/xml; charset=iso8859-1"].each do |ct|
190
+ @request.send(:format_from_mimetype, ct).should == :xml
191
+ end
192
+ end
193
+
194
+ it 'should handle application/xml' do
195
+ ["application/xml", "application/xml; charset=iso8859-1"].each do |ct|
196
+ @request.send(:format_from_mimetype, ct).should == :xml
197
+ end
198
+ end
199
+
200
+ it 'should handle text/json' do
201
+ ["text/json", "text/json; charset=iso8859-1"].each do |ct|
202
+ @request.send(:format_from_mimetype, ct).should == :json
203
+ end
204
+ end
205
+
206
+ it 'should handle application/json' do
207
+ ["application/json", "application/json; charset=iso8859-1"].each do |ct|
208
+ @request.send(:format_from_mimetype, ct).should == :json
209
+ end
210
+ end
211
+
212
+ it 'should handle text/csv' do
213
+ ["text/csv", "text/csv; charset=iso8859-1"].each do |ct|
214
+ @request.send(:format_from_mimetype, ct).should == :csv
215
+ end
216
+ end
217
+
218
+ it 'should handle application/csv' do
219
+ ["application/csv", "application/csv; charset=iso8859-1"].each do |ct|
220
+ @request.send(:format_from_mimetype, ct).should == :csv
221
+ end
222
+ end
223
+
224
+ it 'should handle text/comma-separated-values' do
225
+ ["text/comma-separated-values", "text/comma-separated-values; charset=iso8859-1"].each do |ct|
226
+ @request.send(:format_from_mimetype, ct).should == :csv
227
+ end
228
+ end
229
+
230
+ it 'should handle text/javascript' do
231
+ ["text/javascript", "text/javascript; charset=iso8859-1"].each do |ct|
232
+ @request.send(:format_from_mimetype, ct).should == :plain
233
+ end
234
+ end
235
+
236
+ it 'should handle application/javascript' do
237
+ ["application/javascript", "application/javascript; charset=iso8859-1"].each do |ct|
238
+ @request.send(:format_from_mimetype, ct).should == :plain
239
+ end
240
+ end
241
+
242
+ it "returns nil for an unrecognized mimetype" do
243
+ @request.send(:format_from_mimetype, "application/atom+xml").should be_nil
244
+ end
245
+
246
+ it "returns nil when using a default parser" do
247
+ @request.options[:parser] = lambda {}
248
+ @request.send(:format_from_mimetype, "text/json").should be_nil
249
+ end
250
+ end
251
+
252
+ describe 'parsing responses' do
253
+ it 'should handle xml automatically' do
254
+ xml = %q[<books><book><id>1234</id><name>Foo Bar!</name></book></books>]
255
+ @request.options[:format] = :xml
256
+ @request.send(:parse_response, xml).should == {'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}}
257
+ end
258
+
259
+ it 'should handle csv automatically' do
260
+ csv=[%q["id","Name"],%q["1234","Foo Bar!"]].join("\n")
261
+ @request.options[:format] = :csv
262
+ @request.send(:parse_response, csv).should == [["id","Name"],["1234","Foo Bar!"]]
263
+ end
264
+
265
+ it 'should handle json automatically' do
266
+ json = %q[{"books": {"book": {"name": "Foo Bar!", "id": "1234"}}}]
267
+ @request.options[:format] = :json
268
+ @request.send(:parse_response, json).should == {'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}}
269
+ end
270
+
271
+ it "should include any HTTP headers in the returned response" do
272
+ @request.options[:format] = :html
273
+ response = stub_response "Content"
274
+ response.initialize_http_header("key" => "value")
275
+
276
+ @request.perform.headers.should == { "key" => ["value"] }
277
+ end
278
+
279
+ if "".respond_to?(:encoding)
280
+
281
+ it "should process charset in content type properly" do
282
+ response = stub_response "Content"
283
+ response.initialize_http_header("Content-Type" => "text/plain;charset = utf-8")
284
+ resp = @request.perform
285
+ resp.body.encoding.should == Encoding.find("UTF-8")
286
+ end
287
+
288
+ it "should process charset in content type properly if it has a different case" do
289
+ response = stub_response "Content"
290
+ response.initialize_http_header("Content-Type" => "text/plain;CHARSET = utf-8")
291
+ resp = @request.perform
292
+ resp.body.encoding.should == Encoding.find("UTF-8")
293
+ end
294
+
295
+ it "should process quoted charset in content type properly" do
296
+ response = stub_response "Content"
297
+ response.initialize_http_header("Content-Type" => "text/plain;charset = \"utf-8\"")
298
+ resp = @request.perform
299
+ resp.body.encoding.should == Encoding.find("UTF-8")
300
+ end
301
+
302
+ it "should process utf-16 charset with little endian bom correctly" do
303
+ @request.options[:assume_utf16_is_big_endian] = true
304
+
305
+ response = stub_response "\xFF\xFEC\x00o\x00n\x00t\x00e\x00n\x00t\x00"
306
+ response.initialize_http_header("Content-Type" => "text/plain;charset = utf-16")
307
+ resp = @request.perform
308
+ resp.body.encoding.should == Encoding.find("UTF-16LE")
309
+ end
310
+
311
+ it "should process utf-16 charset with big endian bom correctly" do
312
+ @request.options[:assume_utf16_is_big_endian] = false
313
+
314
+ response = stub_response "\xFE\xFF\x00C\x00o\x00n\x00t\x00e\x00n\x00t"
315
+ response.initialize_http_header("Content-Type" => "text/plain;charset = utf-16")
316
+ resp = @request.perform
317
+ resp.body.encoding.should == Encoding.find("UTF-16BE")
318
+ end
319
+
320
+ it "should assume utf-16 little endian if options has been chosen" do
321
+ @request.options[:assume_utf16_is_big_endian] = false
322
+
323
+ response = stub_response "C\x00o\x00n\x00t\x00e\x00n\x00t\x00"
324
+ response.initialize_http_header("Content-Type" => "text/plain;charset = utf-16")
325
+ resp = @request.perform
326
+ resp.body.encoding.should == Encoding.find("UTF-16LE")
327
+ end
328
+
329
+
330
+ it "should perform no encoding if the charset is not available" do
331
+
332
+ response = stub_response "Content"
333
+ response.initialize_http_header("Content-Type" => "text/plain;charset = utf-lols")
334
+ resp = @request.perform
335
+ resp.body.should == "Content"
336
+ resp.body.encoding.should == "Content".encoding
337
+ end
338
+
339
+ it "should perform no encoding if the content type is specified but no charset is specified" do
340
+
341
+ response = stub_response "Content"
342
+ response.initialize_http_header("Content-Type" => "text/plain")
343
+ resp = @request.perform
344
+ resp.body.should == "Content"
345
+ resp.body.encoding.should == "Content".encoding
346
+ end
347
+ end
348
+
349
+
350
+ describe 'with non-200 responses' do
351
+ context "3xx responses" do
352
+ it 'returns a valid object for 304 not modified' do
353
+ stub_response '', 304
354
+ resp = @request.perform
355
+ resp.code.should == 304
356
+ resp.body.should == ''
357
+ resp.should be_nil
358
+ end
359
+
360
+ it "redirects if a 300 contains a location header" do
361
+ redirect = stub_response '', 300
362
+ redirect['location'] = 'http://foo.com/foo'
363
+ ok = stub_response('<hash><foo>bar</foo></hash>', 200)
364
+ @http.stub!(:request).and_return(redirect, ok)
365
+ response = @request.perform
366
+ response.request.base_uri.to_s.should == "http://foo.com"
367
+ response.request.path.to_s.should == "http://foo.com/foo"
368
+ response.request.uri.request_uri.should == "/foo"
369
+ response.request.uri.to_s.should == "http://foo.com/foo"
370
+ response.should == {"hash" => {"foo" => "bar"}}
371
+ end
372
+
373
+ it "calls block given to perform with each redirect" do
374
+ @request = HTTParty::Request.new(Net::HTTP::Get, 'http://test.com/redirect', format: :xml)
375
+ FakeWeb.register_uri(:get, "http://test.com/redirect", status: [300, "REDIRECT"], location: "http://api.foo.com/v2")
376
+ FakeWeb.register_uri(:get, "http://api.foo.com/v2", body: "<hash><foo>bar</foo></hash>")
377
+ body = ""
378
+ response = @request.perform { |chunk| body += chunk }
379
+ body.length.should == 27
380
+ end
381
+
382
+ it "redirects if a 300 contains a relative location header" do
383
+ redirect = stub_response '', 300
384
+ redirect['location'] = '/foo/bar'
385
+ ok = stub_response('<hash><foo>bar</foo></hash>', 200)
386
+ @http.stub!(:request).and_return(redirect, ok)
387
+ response = @request.perform
388
+ response.request.base_uri.to_s.should == "http://api.foo.com"
389
+ response.request.path.to_s.should == "/foo/bar"
390
+ response.request.uri.request_uri.should == "/foo/bar"
391
+ response.request.uri.to_s.should == "http://api.foo.com/foo/bar"
392
+ response.should == {"hash" => {"foo" => "bar"}}
393
+ end
394
+
395
+ it "handles multiple redirects and relative location headers on different hosts" do
396
+ @request = HTTParty::Request.new(Net::HTTP::Get, 'http://test.com/redirect', format: :xml)
397
+ FakeWeb.register_uri(:get, "http://test.com/redirect", status: [300, "REDIRECT"], location: "http://api.foo.com/v2")
398
+ FakeWeb.register_uri(:get, "http://api.foo.com/v2", status: [300, "REDIRECT"], location: "/v3")
399
+ FakeWeb.register_uri(:get, "http://api.foo.com/v3", body: "<hash><foo>bar</foo></hash>")
400
+ response = @request.perform
401
+ response.request.base_uri.to_s.should == "http://api.foo.com"
402
+ response.request.path.to_s.should == "/v3"
403
+ response.request.uri.request_uri.should == "/v3"
404
+ response.request.uri.to_s.should == "http://api.foo.com/v3"
405
+ response.should == {"hash" => {"foo" => "bar"}}
406
+ end
407
+
408
+ it "returns the HTTParty::Response when the 300 does not contain a location header" do
409
+ stub_response '', 300
410
+ HTTParty::Response.should === @request.perform
411
+ end
412
+ end
413
+
414
+ it 'should return a valid object for 4xx response' do
415
+ stub_response '<foo><bar>yes</bar></foo>', 401
416
+ resp = @request.perform
417
+ resp.code.should == 401
418
+ resp.body.should == "<foo><bar>yes</bar></foo>"
419
+ resp['foo']['bar'].should == "yes"
420
+ end
421
+
422
+ it 'should return a valid object for 5xx response' do
423
+ stub_response '<foo><bar>error</bar></foo>', 500
424
+ resp = @request.perform
425
+ resp.code.should == 500
426
+ resp.body.should == "<foo><bar>error</bar></foo>"
427
+ resp['foo']['bar'].should == "error"
428
+ end
429
+
430
+ it "parses response lazily so codes can be checked prior" do
431
+ stub_response 'not xml', 500
432
+ @request.options[:format] = :xml
433
+ lambda {
434
+ response = @request.perform
435
+ response.code.should == 500
436
+ response.body.should == 'not xml'
437
+ }.should_not raise_error
438
+ end
439
+ end
440
+ end
441
+
442
+ it "should not attempt to parse empty responses" do
443
+ [204, 304].each do |code|
444
+ stub_response "", code
445
+
446
+ @request.options[:format] = :xml
447
+ @request.perform.should be_nil
448
+ end
449
+ end
450
+
451
+ it "should not fail for missing mime type" do
452
+ stub_response "Content for you"
453
+ @request.options[:format] = :html
454
+ @request.perform.should == 'Content for you'
455
+ end
456
+
457
+ describe "a request that 302 redirects" do
458
+ before(:each) do
459
+ @redirect = stub_response("", 302)
460
+ @redirect['location'] = '/foo'
461
+
462
+ @ok = stub_response('<hash><foo>bar</foo></hash>', 200)
463
+ end
464
+
465
+ describe "once" do
466
+ before(:each) do
467
+ @http.stub!(:request).and_return(@redirect, @ok)
468
+ end
469
+
470
+ it "should be handled by GET transparently" do
471
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
472
+ end
473
+
474
+ it "should be handled by POST transparently" do
475
+ @request.http_method = Net::HTTP::Post
476
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
477
+ end
478
+
479
+ it "should be handled by DELETE transparently" do
480
+ @request.http_method = Net::HTTP::Delete
481
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
482
+ end
483
+
484
+ it "should be handled by MOVE transparently" do
485
+ @request.http_method = Net::HTTP::Move
486
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
487
+ end
488
+
489
+ it "should be handled by COPY transparently" do
490
+ @request.http_method = Net::HTTP::Copy
491
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
492
+ end
493
+
494
+ it "should be handled by PATCH transparently" do
495
+ @request.http_method = Net::HTTP::Patch
496
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
497
+ end
498
+
499
+ it "should be handled by PUT transparently" do
500
+ @request.http_method = Net::HTTP::Put
501
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
502
+ end
503
+
504
+ it "should be handled by HEAD transparently" do
505
+ @request.http_method = Net::HTTP::Head
506
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
507
+ end
508
+
509
+ it "should be handled by OPTIONS transparently" do
510
+ @request.http_method = Net::HTTP::Options
511
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
512
+ end
513
+
514
+ it "should keep track of cookies between redirects" do
515
+ @redirect['Set-Cookie'] = 'foo=bar; name=value; HTTPOnly'
516
+ @request.perform
517
+ @request.options[:headers]['Cookie'].should match(/foo=bar/)
518
+ @request.options[:headers]['Cookie'].should match(/name=value/)
519
+ end
520
+
521
+ it 'should update cookies with rediects' do
522
+ @request.options[:headers] = {'Cookie'=> 'foo=bar;'}
523
+ @redirect['Set-Cookie'] = 'foo=tar;'
524
+ @request.perform
525
+ @request.options[:headers]['Cookie'].should match(/foo=tar/)
526
+ end
527
+
528
+ it 'should keep cookies between rediects' do
529
+ @request.options[:headers] = {'Cookie'=> 'keep=me'}
530
+ @redirect['Set-Cookie'] = 'foo=tar;'
531
+ @request.perform
532
+ @request.options[:headers]['Cookie'].should match(/keep=me/)
533
+ end
534
+
535
+ it "should handle multiple Set-Cookie headers between redirects" do
536
+ @redirect.add_field 'set-cookie', 'foo=bar; name=value; HTTPOnly'
537
+ @redirect.add_field 'set-cookie', 'one=1; two=2; HTTPOnly'
538
+ @request.perform
539
+ @request.options[:headers]['Cookie'].should match(/foo=bar/)
540
+ @request.options[:headers]['Cookie'].should match(/name=value/)
541
+ @request.options[:headers]['Cookie'].should match(/one=1/)
542
+ @request.options[:headers]['Cookie'].should match(/two=2/)
543
+ end
544
+
545
+ it 'should make resulting request a get request if it not already' do
546
+ @request.http_method = Net::HTTP::Delete
547
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
548
+ @request.http_method.should == Net::HTTP::Get
549
+ end
550
+
551
+ it 'should not make resulting request a get request if options[:maintain_method_across_redirects] is true' do
552
+ @request.options[:maintain_method_across_redirects] = true
553
+ @request.http_method = Net::HTTP::Delete
554
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
555
+ @request.http_method.should == Net::HTTP::Delete
556
+ end
557
+
558
+ it 'should log the redirection' do
559
+ logger_double = double
560
+ logger_double.should_receive(:info).twice
561
+ @request.options[:logger] = logger_double
562
+ @request.perform
563
+ end
564
+ end
565
+
566
+ describe "infinitely" do
567
+ before(:each) do
568
+ @http.stub!(:request).and_return(@redirect)
569
+ end
570
+
571
+ it "should raise an exception" do
572
+ lambda { @request.perform }.should raise_error(HTTParty::RedirectionTooDeep)
573
+ end
574
+ end
575
+ end
576
+
577
+ describe "a request that 303 redirects" do
578
+ before(:each) do
579
+ @redirect = stub_response("", 303)
580
+ @redirect['location'] = '/foo'
581
+
582
+ @ok = stub_response('<hash><foo>bar</foo></hash>', 200)
583
+ end
584
+
585
+ describe "once" do
586
+ before(:each) do
587
+ @http.stub!(:request).and_return(@redirect, @ok)
588
+ end
589
+
590
+ it "should be handled by GET transparently" do
591
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
592
+ end
593
+
594
+ it "should be handled by POST transparently" do
595
+ @request.http_method = Net::HTTP::Post
596
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
597
+ end
598
+
599
+ it "should be handled by DELETE transparently" do
600
+ @request.http_method = Net::HTTP::Delete
601
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
602
+ end
603
+
604
+ it "should be handled by MOVE transparently" do
605
+ @request.http_method = Net::HTTP::Move
606
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
607
+ end
608
+
609
+ it "should be handled by COPY transparently" do
610
+ @request.http_method = Net::HTTP::Copy
611
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
612
+ end
613
+
614
+ it "should be handled by PATCH transparently" do
615
+ @request.http_method = Net::HTTP::Patch
616
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
617
+ end
618
+
619
+ it "should be handled by PUT transparently" do
620
+ @request.http_method = Net::HTTP::Put
621
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
622
+ end
623
+
624
+ it "should be handled by HEAD transparently" do
625
+ @request.http_method = Net::HTTP::Head
626
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
627
+ end
628
+
629
+ it "should be handled by OPTIONS transparently" do
630
+ @request.http_method = Net::HTTP::Options
631
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
632
+ end
633
+
634
+ it "should keep track of cookies between redirects" do
635
+ @redirect['Set-Cookie'] = 'foo=bar; name=value; HTTPOnly'
636
+ @request.perform
637
+ @request.options[:headers]['Cookie'].should match(/foo=bar/)
638
+ @request.options[:headers]['Cookie'].should match(/name=value/)
639
+ end
640
+
641
+ it 'should update cookies with rediects' do
642
+ @request.options[:headers] = {'Cookie'=> 'foo=bar;'}
643
+ @redirect['Set-Cookie'] = 'foo=tar;'
644
+ @request.perform
645
+ @request.options[:headers]['Cookie'].should match(/foo=tar/)
646
+ end
647
+
648
+ it 'should keep cookies between rediects' do
649
+ @request.options[:headers] = {'Cookie'=> 'keep=me'}
650
+ @redirect['Set-Cookie'] = 'foo=tar;'
651
+ @request.perform
652
+ @request.options[:headers]['Cookie'].should match(/keep=me/)
653
+ end
654
+
655
+ it "should handle multiple Set-Cookie headers between redirects" do
656
+ @redirect.add_field 'set-cookie', 'foo=bar; name=value; HTTPOnly'
657
+ @redirect.add_field 'set-cookie', 'one=1; two=2; HTTPOnly'
658
+ @request.perform
659
+ @request.options[:headers]['Cookie'].should match(/foo=bar/)
660
+ @request.options[:headers]['Cookie'].should match(/name=value/)
661
+ @request.options[:headers]['Cookie'].should match(/one=1/)
662
+ @request.options[:headers]['Cookie'].should match(/two=2/)
663
+ end
664
+
665
+ it 'should make resulting request a get request if it not already' do
666
+ @request.http_method = Net::HTTP::Delete
667
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
668
+ @request.http_method.should == Net::HTTP::Get
669
+ end
670
+
671
+ it 'should make resulting request a get request if options[:maintain_method_across_redirects] is false' do
672
+ @request.options[:maintain_method_across_redirects] = false
673
+ @request.http_method = Net::HTTP::Delete
674
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
675
+ @request.http_method.should == Net::HTTP::Get
676
+ end
677
+
678
+ it 'should make resulting request a get request if options[:maintain_method_across_redirects] is true but options[:resend_on_redirect] is false' do
679
+ @request.options[:maintain_method_across_redirects] = true
680
+ @request.options[:resend_on_redirect] = false
681
+ @request.http_method = Net::HTTP::Delete
682
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
683
+ @request.http_method.should == Net::HTTP::Get
684
+ end
685
+
686
+ it 'should not make resulting request a get request if options[:maintain_method_across_redirects] and options[:resend_on_redirect] is true' do
687
+ @request.options[:maintain_method_across_redirects] = true
688
+ @request.options[:resend_on_redirect] = true
689
+ @request.http_method = Net::HTTP::Delete
690
+ @request.perform.should == {"hash" => {"foo" => "bar"}}
691
+ @request.http_method.should == Net::HTTP::Delete
692
+ end
693
+
694
+ it 'should log the redirection' do
695
+ logger_double = double
696
+ logger_double.should_receive(:info).twice
697
+ @request.options[:logger] = logger_double
698
+ @request.perform
699
+ end
700
+ end
701
+
702
+ describe "infinitely" do
703
+ before(:each) do
704
+ @http.stub!(:request).and_return(@redirect)
705
+ end
706
+
707
+ it "should raise an exception" do
708
+ lambda { @request.perform }.should raise_error(HTTParty::RedirectionTooDeep)
709
+ end
710
+ end
711
+ end
712
+
713
+ describe "#handle_deflation" do
714
+ context "context-encoding" do
715
+ before do
716
+ @request.options[:format] = :html
717
+ @last_response = mock()
718
+ @last_response.stub!(:body).and_return('')
719
+ end
720
+
721
+ it "should inflate the gzipped body with content-encoding: gzip" do
722
+ @last_response.stub!(:[]).with("content-encoding").and_return("gzip")
723
+ @request.stub!(:last_response).and_return(@last_response)
724
+ Zlib::GzipReader.should_receive(:new).and_return(StringIO.new(''))
725
+ @request.last_response.should_receive(:delete).with('content-encoding')
726
+ @request.send(:handle_deflation)
727
+ end
728
+
729
+ it "should inflate the gzipped body with content-encoding: x-gzip" do
730
+ @last_response.stub!(:[]).with("content-encoding").and_return("x-gzip")
731
+ @request.stub!(:last_response).and_return(@last_response)
732
+ Zlib::GzipReader.should_receive(:new).and_return(StringIO.new(''))
733
+ @request.last_response.should_receive(:delete).with('content-encoding')
734
+ @request.send(:handle_deflation)
735
+ end
736
+
737
+ it "should inflate the deflated body" do
738
+ @last_response.stub!(:[]).with("content-encoding").and_return("deflate")
739
+ @request.stub!(:last_response).and_return(@last_response)
740
+ Zlib::Inflate.should_receive(:inflate).and_return('')
741
+ @request.last_response.should_receive(:delete).with('content-encoding')
742
+ @request.send(:handle_deflation)
743
+ end
744
+ end
745
+ end
746
+
747
+ context "with POST http method" do
748
+ it "should raise argument error if query is not a hash" do
749
+ lambda {
750
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', format: :xml, query: 'astring').perform
751
+ }.should raise_error(ArgumentError)
752
+ end
753
+ end
754
+
755
+ describe "argument validation" do
756
+ it "should raise argument error if basic_auth and digest_auth are both present" do
757
+ lambda {
758
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', basic_auth: {}, digest_auth: {}).perform
759
+ }.should raise_error(ArgumentError, "only one authentication method, :basic_auth or :digest_auth may be used at a time")
760
+ end
761
+
762
+ it "should raise argument error if basic_auth is not a hash" do
763
+ lambda {
764
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', basic_auth: ["foo", "bar"]).perform
765
+ }.should raise_error(ArgumentError, ":basic_auth must be a hash")
766
+ end
767
+
768
+ it "should raise argument error if digest_auth is not a hash" do
769
+ lambda {
770
+ HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', digest_auth: ["foo", "bar"]).perform
771
+ }.should raise_error(ArgumentError, ":digest_auth must be a hash")
772
+ end
773
+ end
774
+ end