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