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,193 @@
1
+ require_relative '../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
+ @request_object = HTTParty::Request.new Net::HTTP::Get, '/'
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(@request_object, @response_object, @parsed_response)
14
+ end
15
+
16
+ describe ".underscore" do
17
+ it "works with one capitalized word" do
18
+ HTTParty::Response.underscore("Accepted").should == "accepted"
19
+ end
20
+
21
+ it "works with titlecase" do
22
+ HTTParty::Response.underscore("BadGateway").should == "bad_gateway"
23
+ end
24
+
25
+ it "works with all caps" do
26
+ HTTParty::Response.underscore("OK").should == "ok"
27
+ end
28
+ end
29
+
30
+ describe "initialization" do
31
+ it "should set the Net::HTTP Response" do
32
+ @response.response.should == @response_object
33
+ end
34
+
35
+ it "should set body" do
36
+ @response.body.should == @response_object.body
37
+ end
38
+
39
+ it "should set code" do
40
+ @response.code.should.to_s == @response_object.code
41
+ end
42
+
43
+ it "should set code as a Fixnum" do
44
+ @response.code.should be_an_instance_of(Fixnum)
45
+ end
46
+ end
47
+
48
+ it "returns response headers" do
49
+ response = HTTParty::Response.new(@request_object, @response_object, @parsed_response)
50
+ response.headers.should == {'last-modified' => [@last_modified], 'content-length' => [@content_length]}
51
+ end
52
+
53
+ it "should send missing methods to delegate" do
54
+ response = HTTParty::Response.new(@request_object, @response_object, {'foo' => 'bar'})
55
+ response['foo'].should == 'bar'
56
+ end
57
+
58
+ it "should respond_to? methods it supports" do
59
+ response = HTTParty::Response.new(@request_object, @response_object, {'foo' => 'bar'})
60
+ response.respond_to?(:parsed_response).should be_true
61
+ end
62
+
63
+ it "should be able to iterate if it is array" do
64
+ response = HTTParty::Response.new(@request_object, @response_object, [{'foo' => 'bar'}, {'foo' => 'baz'}])
65
+ response.size.should == 2
66
+ expect {
67
+ response.each { |item| }
68
+ }.to_not raise_error
69
+ end
70
+
71
+ it "allows headers to be accessed by mixed-case names in hash notation" do
72
+ response = HTTParty::Response.new(@request_object, @response_object, @parsed_response)
73
+ response.headers['Content-LENGTH'].should == @content_length
74
+ end
75
+
76
+ it "returns a comma-delimited value when multiple values exist" do
77
+ @response_object.add_field 'set-cookie', 'csrf_id=12345; path=/'
78
+ @response_object.add_field 'set-cookie', '_github_ses=A123CdE; path=/'
79
+ response = HTTParty::Response.new(@request_object, @response_object, @parsed_response)
80
+ response.headers['set-cookie'].should == "csrf_id=12345; path=/, _github_ses=A123CdE; path=/"
81
+ end
82
+
83
+ # Backwards-compatibility - previously, #headers returned a Hash
84
+ it "responds to hash methods" do
85
+ response = HTTParty::Response.new(@request_object, @response_object, @parsed_response)
86
+ hash_methods = {}.methods - response.headers.methods
87
+ hash_methods.each do |method_name|
88
+ response.headers.respond_to?(method_name).should be_true
89
+ end
90
+ end
91
+
92
+ xit "should allow hashes to be accessed with dot notation" do
93
+ response = HTTParty::Response.new(@request_object, {'foo' => 'bar'}, "{foo:'bar'}", 200, 'OK')
94
+ response.foo.should == 'bar'
95
+ end
96
+
97
+ xit "should allow nested hashes to be accessed with dot notation" do
98
+ response = HTTParty::Response.new(@request_object, {'foo' => {'bar' => 'baz'}}, "{foo: {bar:'baz'}}", 200, 'OK')
99
+ response.foo.should == {'bar' => 'baz'}
100
+ response.foo.bar.should == 'baz'
101
+ end
102
+
103
+ describe "semantic methods for response codes" do
104
+ def response_mock(klass)
105
+ r = klass.new('', '', '')
106
+ r.stub(:body)
107
+ r
108
+ end
109
+
110
+ context "major codes" do
111
+ it "is information" do
112
+ net_response = response_mock(Net::HTTPInformation)
113
+ response = HTTParty::Response.new(@request_object, net_response, '')
114
+ response.information?.should be_true
115
+ end
116
+
117
+ it "is success" do
118
+ net_response = response_mock(Net::HTTPSuccess)
119
+ response = HTTParty::Response.new(@request_object, net_response, '')
120
+ response.success?.should be_true
121
+ end
122
+
123
+ it "is redirection" do
124
+ net_response = response_mock(Net::HTTPRedirection)
125
+ response = HTTParty::Response.new(@request_object, net_response, '')
126
+ response.redirection?.should be_true
127
+ end
128
+
129
+ it "is client error" do
130
+ net_response = response_mock(Net::HTTPClientError)
131
+ response = HTTParty::Response.new(@request_object, net_response, '')
132
+ response.client_error?.should be_true
133
+ end
134
+
135
+ it "is server error" do
136
+ net_response = response_mock(Net::HTTPServerError)
137
+ response = HTTParty::Response.new(@request_object, net_response, '')
138
+ response.server_error?.should be_true
139
+ end
140
+ end
141
+
142
+ context "for specific codes" do
143
+ SPECIFIC_CODES = {
144
+ :accepted? => Net::HTTPAccepted,
145
+ :bad_gateway? => Net::HTTPBadGateway,
146
+ :bad_request? => Net::HTTPBadRequest,
147
+ :conflict? => Net::HTTPConflict,
148
+ :continue? => Net::HTTPContinue,
149
+ :created? => Net::HTTPCreated,
150
+ :expectation_failed? => Net::HTTPExpectationFailed,
151
+ :forbidden? => Net::HTTPForbidden,
152
+ :found? => Net::HTTPFound,
153
+ :gateway_time_out? => Net::HTTPGatewayTimeOut,
154
+ :gone? => Net::HTTPGone,
155
+ :internal_server_error? => Net::HTTPInternalServerError,
156
+ :length_required? => Net::HTTPLengthRequired,
157
+ :method_not_allowed? => Net::HTTPMethodNotAllowed,
158
+ :moved_permanently? => Net::HTTPMovedPermanently,
159
+ :multiple_choice? => Net::HTTPMultipleChoice,
160
+ :no_content? => Net::HTTPNoContent,
161
+ :non_authoritative_information? => Net::HTTPNonAuthoritativeInformation,
162
+ :not_acceptable? => Net::HTTPNotAcceptable,
163
+ :not_found? => Net::HTTPNotFound,
164
+ :not_implemented? => Net::HTTPNotImplemented,
165
+ :not_modified? => Net::HTTPNotModified,
166
+ :ok? => Net::HTTPOK,
167
+ :partial_content? => Net::HTTPPartialContent,
168
+ :payment_required? => Net::HTTPPaymentRequired,
169
+ :precondition_failed? => Net::HTTPPreconditionFailed,
170
+ :proxy_authentication_required? => Net::HTTPProxyAuthenticationRequired,
171
+ :request_entity_too_large? => Net::HTTPRequestEntityTooLarge,
172
+ :request_time_out? => Net::HTTPRequestTimeOut,
173
+ :request_uri_too_long? => Net::HTTPRequestURITooLong,
174
+ :requested_range_not_satisfiable? => Net::HTTPRequestedRangeNotSatisfiable,
175
+ :reset_content? => Net::HTTPResetContent,
176
+ :see_other? => Net::HTTPSeeOther,
177
+ :service_unavailable? => Net::HTTPServiceUnavailable,
178
+ :switch_protocol? => Net::HTTPSwitchProtocol,
179
+ :temporary_redirect? => Net::HTTPTemporaryRedirect,
180
+ :unauthorized? => Net::HTTPUnauthorized,
181
+ :unsupported_media_type? => Net::HTTPUnsupportedMediaType,
182
+ :use_proxy? => Net::HTTPUseProxy,
183
+ :version_not_supported? => Net::HTTPVersionNotSupported
184
+ }.each do |method, klass|
185
+ it "responds to #{method}" do
186
+ net_response = response_mock(klass)
187
+ response = HTTParty::Response.new(@request_object, net_response, '')
188
+ response.__send__(method).should be_true
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,54 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe HTTParty::Request do
4
+ context "SSL certificate verification" do
5
+ before do
6
+ FakeWeb.allow_net_connect = true # enable network connections just for this test
7
+ end
8
+
9
+ after do
10
+ FakeWeb.allow_net_connect = false # Restore allow_net_connect value for testing
11
+ end
12
+
13
+ it "should work with when no trusted CA list is specified" do
14
+ ssl_verify_test(nil, nil, "selfsigned.crt").should == {'success' => true}
15
+ end
16
+
17
+ it "should work with when no trusted CA list is specified, even with a bogus hostname" do
18
+ ssl_verify_test(nil, nil, "bogushost.crt").should == {'success' => true}
19
+ end
20
+
21
+ it "should work when using ssl_ca_file with a self-signed CA" do
22
+ ssl_verify_test(:ssl_ca_file, "selfsigned.crt", "selfsigned.crt").should == {'success' => true}
23
+ end
24
+
25
+ it "should work when using ssl_ca_file with a certificate authority" do
26
+ ssl_verify_test(:ssl_ca_file, "ca.crt", "server.crt").should == {'success' => true}
27
+ end
28
+ it "should work when using ssl_ca_path with a certificate authority" do
29
+ ssl_verify_test(:ssl_ca_path, ".", "server.crt").should == {'success' => true}
30
+ end
31
+
32
+ it "should fail when using ssl_ca_file and the server uses an unrecognized certificate authority" do
33
+ lambda do
34
+ ssl_verify_test(:ssl_ca_file, "ca.crt", "selfsigned.crt")
35
+ end.should raise_error(OpenSSL::SSL::SSLError)
36
+ end
37
+ it "should fail when using ssl_ca_path and the server uses an unrecognized certificate authority" do
38
+ lambda do
39
+ ssl_verify_test(:ssl_ca_path, ".", "selfsigned.crt")
40
+ end.should raise_error(OpenSSL::SSL::SSLError)
41
+ end
42
+
43
+ it "should fail when using ssl_ca_file and the server uses a bogus hostname" do
44
+ lambda do
45
+ ssl_verify_test(:ssl_ca_file, "ca.crt", "bogushost.crt")
46
+ end.should raise_error(OpenSSL::SSL::SSLError)
47
+ end
48
+ it "should fail when using ssl_ca_path and the server uses a bogus hostname" do
49
+ lambda do
50
+ ssl_verify_test(:ssl_ca_path, ".", "bogushost.crt")
51
+ end.should raise_error(OpenSSL::SSL::SSLError)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,621 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe HTTParty do
4
+ before(:each) do
5
+ @klass = Class.new
6
+ @klass.instance_eval { include HTTParty }
7
+ end
8
+
9
+ describe "AllowedFormats deprecated" do
10
+ before do
11
+ Kernel.stub(:warn)
12
+ end
13
+ it "warns with a deprecation message" do
14
+ Kernel.should_receive(:warn).with("Deprecated: Use HTTParty::Parser::SupportedFormats")
15
+ HTTParty::AllowedFormats
16
+ end
17
+
18
+ it "returns HTTPart::Parser::SupportedFormats" do
19
+ HTTParty::AllowedFormats.should == HTTParty::Parser::SupportedFormats
20
+ end
21
+ end
22
+
23
+ describe "pem" do
24
+
25
+ it 'should set the pem content' do
26
+ @klass.pem 'PEM-CONTENT'
27
+ @klass.default_options[:pem].should == 'PEM-CONTENT'
28
+ end
29
+
30
+ it "should set the password to nil if it's not provided" do
31
+ @klass.pem 'PEM-CONTENT'
32
+ @klass.default_options[:pem_password].should be_nil
33
+ end
34
+
35
+ it 'should set the password' do
36
+ @klass.pem 'PEM-CONTENT', 'PASSWORD'
37
+ @klass.default_options[:pem_password].should == 'PASSWORD'
38
+ end
39
+
40
+ end
41
+
42
+ describe "base uri" do
43
+ before(:each) do
44
+ @klass.base_uri('api.foo.com/v1')
45
+ end
46
+
47
+ it "should have reader" do
48
+ @klass.base_uri.should == 'http://api.foo.com/v1'
49
+ end
50
+
51
+ it 'should have writer' do
52
+ @klass.base_uri('http://api.foobar.com')
53
+ @klass.base_uri.should == 'http://api.foobar.com'
54
+ end
55
+
56
+ it 'should not modify the parameter during assignment' do
57
+ uri = 'http://api.foobar.com'
58
+ @klass.base_uri(uri)
59
+ uri.should == 'http://api.foobar.com'
60
+ end
61
+ end
62
+
63
+ describe ".disable_rails_query_string_format" do
64
+ it "sets the query string normalizer to HTTParty::Request::NON_RAILS_QUERY_STRING_NORMALIZER" do
65
+ @klass.disable_rails_query_string_format
66
+ @klass.default_options[:query_string_normalizer].should == HTTParty::Request::NON_RAILS_QUERY_STRING_NORMALIZER
67
+ end
68
+ end
69
+
70
+ describe ".normalize_base_uri" do
71
+ it "should add http if not present for non ssl requests" do
72
+ uri = HTTParty.normalize_base_uri('api.foobar.com')
73
+ uri.should == 'http://api.foobar.com'
74
+ end
75
+
76
+ it "should add https if not present for ssl requests" do
77
+ uri = HTTParty.normalize_base_uri('api.foo.com/v1:443')
78
+ uri.should == 'https://api.foo.com/v1:443'
79
+ end
80
+
81
+ it "should not remove https for ssl requests" do
82
+ uri = HTTParty.normalize_base_uri('https://api.foo.com/v1:443')
83
+ uri.should == 'https://api.foo.com/v1:443'
84
+ end
85
+
86
+ it 'should not modify the parameter' do
87
+ uri = 'http://api.foobar.com'
88
+ HTTParty.normalize_base_uri(uri)
89
+ uri.should == 'http://api.foobar.com'
90
+ end
91
+ end
92
+
93
+ describe "headers" do
94
+ def expect_headers(header={})
95
+ HTTParty::Request.should_receive(:new) \
96
+ .with(anything, anything, hash_including({ :headers => header })) \
97
+ .and_return(mock("mock response", :perform => nil))
98
+ end
99
+
100
+ it "should default to empty hash" do
101
+ @klass.headers.should == {}
102
+ end
103
+
104
+ it "should be able to be updated" do
105
+ init_headers = {:foo => 'bar', :baz => 'spax'}
106
+ @klass.headers init_headers
107
+ @klass.headers.should == init_headers
108
+ end
109
+
110
+ it "uses the class headers when sending a request" do
111
+ expect_headers(:foo => 'bar')
112
+ @klass.headers(:foo => 'bar')
113
+ @klass.get('')
114
+ end
115
+
116
+ it "overwrites class headers when passing in headers" do
117
+ expect_headers(:baz => 'spax')
118
+ @klass.headers(:foo => 'bar')
119
+ @klass.get('', :headers => {:baz => 'spax'})
120
+ end
121
+
122
+ context "with cookies" do
123
+ it 'utilizes the class-level cookies' do
124
+ expect_headers(:foo => 'bar', 'cookie' => 'type=snickerdoodle')
125
+ @klass.headers(:foo => 'bar')
126
+ @klass.cookies(:type => 'snickerdoodle')
127
+ @klass.get('')
128
+ end
129
+
130
+ it 'adds cookies to the headers' do
131
+ expect_headers(:foo => 'bar', 'cookie' => 'type=snickerdoodle')
132
+ @klass.headers(:foo => 'bar')
133
+ @klass.get('', :cookies => {:type => 'snickerdoodle'})
134
+ end
135
+
136
+ it 'adds optional cookies to the optional headers' do
137
+ expect_headers(:baz => 'spax', 'cookie' => 'type=snickerdoodle')
138
+ @klass.get('', :cookies => {:type => 'snickerdoodle'}, :headers => {:baz => 'spax'})
139
+ end
140
+ end
141
+ end
142
+
143
+ describe "cookies" do
144
+ def expect_cookie_header(s)
145
+ HTTParty::Request.should_receive(:new) \
146
+ .with(anything, anything, hash_including({ :headers => { "cookie" => s } })) \
147
+ .and_return(mock("mock response", :perform => nil))
148
+ end
149
+
150
+ it "should not be in the headers by default" do
151
+ HTTParty::Request.stub!(:new).and_return(stub(nil, :perform => nil))
152
+ @klass.get("")
153
+ @klass.headers.keys.should_not include("cookie")
154
+ end
155
+
156
+ it "should raise an ArgumentError if passed a non-Hash" do
157
+ lambda do
158
+ @klass.cookies("nonsense")
159
+ end.should raise_error(ArgumentError)
160
+ end
161
+
162
+ it "should allow a cookie to be specified with a one-off request" do
163
+ expect_cookie_header "type=snickerdoodle"
164
+ @klass.get("", :cookies => { :type => "snickerdoodle" })
165
+ end
166
+
167
+ describe "when a cookie is set at the class level" do
168
+ before(:each) do
169
+ @klass.cookies({ :type => "snickerdoodle" })
170
+ end
171
+
172
+ it "should include that cookie in the request" do
173
+ expect_cookie_header "type=snickerdoodle"
174
+ @klass.get("")
175
+ end
176
+
177
+ it "should pass the proper cookies when requested multiple times" do
178
+ 2.times do
179
+ expect_cookie_header "type=snickerdoodle"
180
+ @klass.get("")
181
+ end
182
+ end
183
+
184
+ it "should allow the class defaults to be overridden" do
185
+ expect_cookie_header "type=chocolate_chip"
186
+
187
+ @klass.get("", :cookies => { :type => "chocolate_chip" })
188
+ end
189
+ end
190
+
191
+ describe "in a class with multiple methods that use different cookies" do
192
+ before(:each) do
193
+ @klass.instance_eval do
194
+ def first_method
195
+ get("first_method", :cookies => { :first_method_cookie => "foo" })
196
+ end
197
+
198
+ def second_method
199
+ get("second_method", :cookies => { :second_method_cookie => "foo" })
200
+ end
201
+ end
202
+ end
203
+
204
+ it "should not allow cookies used in one method to carry over into other methods" do
205
+ expect_cookie_header "first_method_cookie=foo"
206
+ @klass.first_method
207
+
208
+ expect_cookie_header "second_method_cookie=foo"
209
+ @klass.second_method
210
+ end
211
+ end
212
+ end
213
+
214
+ describe "default params" do
215
+ it "should default to empty hash" do
216
+ @klass.default_params.should == {}
217
+ end
218
+
219
+ it "should be able to be updated" do
220
+ new_defaults = {:foo => 'bar', :baz => 'spax'}
221
+ @klass.default_params new_defaults
222
+ @klass.default_params.should == new_defaults
223
+ end
224
+ end
225
+
226
+ describe "default timeout" do
227
+ it "should default to nil" do
228
+ @klass.default_options[:timeout].should == nil
229
+ end
230
+
231
+ it "should support updating" do
232
+ @klass.default_timeout 10
233
+ @klass.default_options[:timeout].should == 10
234
+ end
235
+
236
+ it "should support floats" do
237
+ @klass.default_timeout 0.5
238
+ @klass.default_options[:timeout].should == 0.5
239
+ end
240
+ end
241
+
242
+ describe "debug_output" do
243
+ it "stores the given stream as a default_option" do
244
+ @klass.debug_output $stdout
245
+ @klass.default_options[:debug_output].should == $stdout
246
+ end
247
+
248
+ it "stores the $stderr stream by default" do
249
+ @klass.debug_output
250
+ @klass.default_options[:debug_output].should == $stderr
251
+ end
252
+ end
253
+
254
+ describe "basic http authentication" do
255
+ it "should work" do
256
+ @klass.basic_auth 'foobar', 'secret'
257
+ @klass.default_options[:basic_auth].should == {:username => 'foobar', :password => 'secret'}
258
+ end
259
+ end
260
+
261
+ describe "digest http authentication" do
262
+ it "should work" do
263
+ @klass.digest_auth 'foobar', 'secret'
264
+ @klass.default_options[:digest_auth].should == {:username => 'foobar', :password => 'secret'}
265
+ end
266
+ end
267
+
268
+ describe "parser" do
269
+ class CustomParser
270
+ def self.parse(body)
271
+ return {:sexy => true}
272
+ end
273
+ end
274
+
275
+ let(:parser) do
276
+ Proc.new{ |data, format| CustomParser.parse(data) }
277
+ end
278
+
279
+ it "should set parser options" do
280
+ @klass.parser parser
281
+ @klass.default_options[:parser].should == parser
282
+ end
283
+
284
+ it "should be able parse response with custom parser" do
285
+ @klass.parser parser
286
+ FakeWeb.register_uri(:get, 'http://twitter.com/statuses/public_timeline.xml', :body => 'tweets')
287
+ custom_parsed_response = @klass.get('http://twitter.com/statuses/public_timeline.xml')
288
+ custom_parsed_response[:sexy].should == true
289
+ end
290
+
291
+ it "raises UnsupportedFormat when the parser cannot handle the format" do
292
+ @klass.format :json
293
+ class MyParser < HTTParty::Parser
294
+ SupportedFormats = {}
295
+ end unless defined?(MyParser)
296
+ expect do
297
+ @klass.parser MyParser
298
+ end.to raise_error(HTTParty::UnsupportedFormat)
299
+ end
300
+
301
+ it 'does not validate format whe custom parser is a proc' do
302
+ expect do
303
+ @klass.format :json
304
+ @klass.parser lambda {|body, format|}
305
+ end.to_not raise_error(HTTParty::UnsupportedFormat)
306
+ end
307
+ end
308
+
309
+ describe "format" do
310
+ it "should allow xml" do
311
+ @klass.format :xml
312
+ @klass.default_options[:format].should == :xml
313
+ end
314
+
315
+ it "should allow json" do
316
+ @klass.format :json
317
+ @klass.default_options[:format].should == :json
318
+ end
319
+
320
+ it "should allow yaml" do
321
+ @klass.format :yaml
322
+ @klass.default_options[:format].should == :yaml
323
+ end
324
+
325
+ it "should allow plain" do
326
+ @klass.format :plain
327
+ @klass.default_options[:format].should == :plain
328
+ end
329
+
330
+ it 'should not allow funky format' do
331
+ lambda do
332
+ @klass.format :foobar
333
+ end.should raise_error(HTTParty::UnsupportedFormat)
334
+ end
335
+
336
+ it 'should only print each format once with an exception' do
337
+ lambda do
338
+ @klass.format :foobar
339
+ end.should raise_error(HTTParty::UnsupportedFormat, "':foobar' Must be one of: html, json, plain, xml, yaml")
340
+ end
341
+
342
+ it 'sets the default parser' do
343
+ @klass.default_options[:parser].should be_nil
344
+ @klass.format :json
345
+ @klass.default_options[:parser].should == HTTParty::Parser
346
+ end
347
+
348
+ it 'does not reset parser to the default parser' do
349
+ my_parser = lambda {}
350
+ @klass.parser my_parser
351
+ @klass.format :json
352
+ @klass.parser.should == my_parser
353
+ end
354
+ end
355
+
356
+ describe "#no_follow" do
357
+ it "sets no_follow to false by default" do
358
+ @klass.no_follow
359
+ @klass.default_options[:no_follow].should be_false
360
+ end
361
+
362
+ it "sets the no_follow option to true" do
363
+ @klass.no_follow true
364
+ @klass.default_options[:no_follow].should be_true
365
+ end
366
+ end
367
+
368
+ describe "#maintain_method_across_redirects" do
369
+ it "sets maintain_method_across_redirects to true by default" do
370
+ @klass.maintain_method_across_redirects
371
+ @klass.default_options[:maintain_method_across_redirects].should be_true
372
+ end
373
+
374
+ it "sets the maintain_method_across_redirects option to false" do
375
+ @klass.maintain_method_across_redirects false
376
+ @klass.default_options[:maintain_method_across_redirects].should be_false
377
+ end
378
+ end
379
+
380
+ describe ".follow_redirects" do
381
+ it "sets follow redirects to true by default" do
382
+ @klass.follow_redirects
383
+ @klass.default_options[:follow_redirects].should be_true
384
+ end
385
+
386
+ it "sets the follow_redirects option to false" do
387
+ @klass.follow_redirects false
388
+ @klass.default_options[:follow_redirects].should be_false
389
+ end
390
+ end
391
+
392
+ describe ".query_string_normalizer" do
393
+ it "sets the query_string_normalizer option" do
394
+ normalizer = proc {}
395
+ @klass.query_string_normalizer normalizer
396
+ @klass.default_options[:query_string_normalizer].should == normalizer
397
+ end
398
+ end
399
+
400
+ describe "with explicit override of automatic redirect handling" do
401
+ before do
402
+ @request = HTTParty::Request.new(Net::HTTP::Get, 'http://api.foo.com/v1', :format => :xml, :no_follow => true)
403
+ @redirect = stub_response 'first redirect', 302
404
+ @redirect['location'] = 'http://foo.com/bar'
405
+ HTTParty::Request.stub(:new => @request)
406
+ end
407
+
408
+ it "should fail with redirected GET" do
409
+ lambda do
410
+ @error = @klass.get('/foo', :no_follow => true)
411
+ end.should raise_error(HTTParty::RedirectionTooDeep) {|e| e.response.body.should == 'first redirect'}
412
+ end
413
+
414
+ it "should fail with redirected POST" do
415
+ lambda do
416
+ @klass.post('/foo', :no_follow => true)
417
+ end.should raise_error(HTTParty::RedirectionTooDeep) {|e| e.response.body.should == 'first redirect'}
418
+ end
419
+
420
+ it "should fail with redirected DELETE" do
421
+ lambda do
422
+ @klass.delete('/foo', :no_follow => true)
423
+ end.should raise_error(HTTParty::RedirectionTooDeep) {|e| e.response.body.should == 'first redirect'}
424
+ end
425
+
426
+ it "should fail with redirected PUT" do
427
+ lambda do
428
+ @klass.put('/foo', :no_follow => true)
429
+ end.should raise_error(HTTParty::RedirectionTooDeep) {|e| e.response.body.should == 'first redirect'}
430
+ end
431
+
432
+ it "should fail with redirected HEAD" do
433
+ lambda do
434
+ @klass.head('/foo', :no_follow => true)
435
+ end.should raise_error(HTTParty::RedirectionTooDeep) {|e| e.response.body.should == 'first redirect'}
436
+ end
437
+
438
+ it "should fail with redirected OPTIONS" do
439
+ lambda do
440
+ @klass.options('/foo', :no_follow => true)
441
+ end.should raise_error(HTTParty::RedirectionTooDeep) {|e| e.response.body.should == 'first redirect'}
442
+ end
443
+ end
444
+
445
+ describe "with multiple class definitions" do
446
+ before(:each) do
447
+ @klass.instance_eval do
448
+ base_uri "http://first.com"
449
+ default_params :one => 1
450
+ end
451
+
452
+ @additional_klass = Class.new
453
+ @additional_klass.instance_eval do
454
+ include HTTParty
455
+ base_uri "http://second.com"
456
+ default_params :two => 2
457
+ end
458
+ end
459
+
460
+ it "should not run over each others options" do
461
+ @klass.default_options.should == { :base_uri => 'http://first.com', :default_params => { :one => 1 } }
462
+ @additional_klass.default_options.should == { :base_uri => 'http://second.com', :default_params => { :two => 2 } }
463
+ end
464
+ end
465
+
466
+ describe "two child classes inheriting from one parent" do
467
+ before(:each) do
468
+ @parent = Class.new do
469
+ include HTTParty
470
+ def self.name
471
+ "Parent"
472
+ end
473
+ end
474
+
475
+ @child1 = Class.new(@parent)
476
+ @child2 = Class.new(@parent)
477
+ end
478
+
479
+ it "does not modify each others inherited attributes" do
480
+ @child1.default_params :joe => "alive"
481
+ @child2.default_params :joe => "dead"
482
+
483
+ @child1.default_options.should == { :default_params => {:joe => "alive"} }
484
+ @child2.default_options.should == { :default_params => {:joe => "dead"} }
485
+
486
+ @parent.default_options.should == { }
487
+ end
488
+
489
+ it "inherits default_options from the superclass" do
490
+ @parent.basic_auth 'user', 'password'
491
+ @child1.default_options.should == {:basic_auth => {:username => 'user', :password => 'password'}}
492
+ @child1.basic_auth 'u', 'p' # modifying child1 has no effect on child2
493
+ @child2.default_options.should == {:basic_auth => {:username => 'user', :password => 'password'}}
494
+ end
495
+
496
+ it "doesn't modify the parent's default options" do
497
+ @parent.basic_auth 'user', 'password'
498
+
499
+ @child1.basic_auth 'u', 'p'
500
+ @child1.default_options.should == {:basic_auth => {:username => 'u', :password => 'p'}}
501
+
502
+ @child1.basic_auth 'email', 'token'
503
+ @child1.default_options.should == {:basic_auth => {:username => 'email', :password => 'token'}}
504
+
505
+ @parent.default_options.should == {:basic_auth => {:username => 'user', :password => 'password'}}
506
+ end
507
+
508
+ it "inherits default_cookies from the parent class" do
509
+ @parent.cookies 'type' => 'chocolate_chip'
510
+ @child1.default_cookies.should == {"type" => "chocolate_chip"}
511
+ @child1.cookies 'type' => 'snickerdoodle'
512
+ @child1.default_cookies.should == {"type" => "snickerdoodle"}
513
+ @child2.default_cookies.should == {"type" => "chocolate_chip"}
514
+ end
515
+
516
+ it "doesn't modify the parent's default cookies" do
517
+ @parent.cookies 'type' => 'chocolate_chip'
518
+
519
+ @child1.cookies 'type' => 'snickerdoodle'
520
+ @child1.default_cookies.should == {"type" => "snickerdoodle"}
521
+
522
+ @parent.default_cookies.should == {"type" => "chocolate_chip"}
523
+ end
524
+ end
525
+
526
+ describe "grand parent with inherited callback" do
527
+ before do
528
+ @grand_parent = Class.new do
529
+ def self.inherited(subclass)
530
+ subclass.instance_variable_set(:@grand_parent, true)
531
+ end
532
+ end
533
+ @parent = Class.new(@grand_parent) do
534
+ include HTTParty
535
+ end
536
+ end
537
+ it "continues running the #inherited on the parent" do
538
+ child = Class.new(@parent)
539
+ child.instance_variable_get(:@grand_parent).should be_true
540
+ end
541
+ end
542
+
543
+ describe "#get" do
544
+ it "should be able to get html" do
545
+ stub_http_response_with('google.html')
546
+ HTTParty.get('http://www.google.com').should == file_fixture('google.html')
547
+ end
548
+
549
+ it "should be able parse response type json automatically" do
550
+ stub_http_response_with('twitter.json')
551
+ tweets = HTTParty.get('http://twitter.com/statuses/public_timeline.json')
552
+ tweets.size.should == 20
553
+ tweets.first['user'].should == {
554
+ "name" => "Pyk",
555
+ "url" => nil,
556
+ "id" => "7694602",
557
+ "description" => nil,
558
+ "protected" => false,
559
+ "screen_name" => "Pyk",
560
+ "followers_count" => 1,
561
+ "location" => "Opera Plaza, California",
562
+ "profile_image_url" => "http://static.twitter.com/images/default_profile_normal.png"
563
+ }
564
+ end
565
+
566
+ it "should be able parse response type xml automatically" do
567
+ stub_http_response_with('twitter.xml')
568
+ tweets = HTTParty.get('http://twitter.com/statuses/public_timeline.xml')
569
+ tweets['statuses'].size.should == 20
570
+ tweets['statuses'].first['user'].should == {
571
+ "name" => "Magic 8 Bot",
572
+ "url" => nil,
573
+ "id" => "17656026",
574
+ "description" => "ask me a question",
575
+ "protected" => "false",
576
+ "screen_name" => "magic8bot",
577
+ "followers_count" => "90",
578
+ "profile_image_url" => "http://s3.amazonaws.com/twitter_production/profile_images/65565851/8ball_large_normal.jpg",
579
+ "location" => nil
580
+ }
581
+ end
582
+
583
+ it "should not get undefined method add_node for nil class for the following xml" do
584
+ stub_http_response_with('undefined_method_add_node_for_nil.xml')
585
+ result = HTTParty.get('http://foobar.com')
586
+ result.should == {"Entities"=>{"href"=>"https://s3-sandbox.parature.com/api/v1/5578/5633/Account", "results"=>"0", "total"=>"0", "page_size"=>"25", "page"=>"1"}}
587
+ end
588
+
589
+ it "should parse empty response fine" do
590
+ stub_http_response_with('empty.xml')
591
+ result = HTTParty.get('http://foobar.com')
592
+ result.should be_nil
593
+ end
594
+
595
+ it "should accept http URIs" do
596
+ stub_http_response_with('google.html')
597
+ lambda do
598
+ HTTParty.get('http://google.com')
599
+ end.should_not raise_error(HTTParty::UnsupportedURIScheme)
600
+ end
601
+
602
+ it "should accept https URIs" do
603
+ stub_http_response_with('google.html')
604
+ lambda do
605
+ HTTParty.get('https://google.com')
606
+ end.should_not raise_error(HTTParty::UnsupportedURIScheme)
607
+ end
608
+
609
+ it "should raise an ArgumentError on URIs that are not http or https" do
610
+ lambda do
611
+ HTTParty.get("file:///there_is_no_party_on/my/filesystem")
612
+ end.should raise_error(HTTParty::UnsupportedURIScheme)
613
+ end
614
+
615
+ it "should raise an InvalidURIError on URIs that can't be parsed at all" do
616
+ lambda do
617
+ HTTParty.get("It's the one that says 'Bad URI'")
618
+ end.should raise_error(URI::InvalidURIError)
619
+ end
620
+ end
621
+ end