yoyle439587298 0.13.0

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