yoyle439587298 0.13.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 (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