dkastner-httparty 0.9.0

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