rest-client 1.6.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rest-client might be problematic. Click here for more details.

@@ -0,0 +1,147 @@
1
+ module RestClient
2
+ # A class that can be instantiated for access to a RESTful resource,
3
+ # including authentication.
4
+ #
5
+ # Example:
6
+ #
7
+ # resource = RestClient::Resource.new('http://some/resource')
8
+ # jpg = resource.get(:accept => 'image/jpg')
9
+ #
10
+ # With HTTP basic authentication:
11
+ #
12
+ # resource = RestClient::Resource.new('http://protected/resource', :user => 'user', :password => 'password')
13
+ # resource.delete
14
+ #
15
+ # With a timeout (seconds):
16
+ #
17
+ # RestClient::Resource.new('http://slow', :timeout => 10)
18
+ #
19
+ # With an open timeout (seconds):
20
+ #
21
+ # RestClient::Resource.new('http://behindfirewall', :open_timeout => 10)
22
+ #
23
+ # You can also use resources to share common headers. For headers keys,
24
+ # symbols are converted to strings. Example:
25
+ #
26
+ # resource = RestClient::Resource.new('http://some/resource', :headers => { :client_version => 1 })
27
+ #
28
+ # This header will be transported as X-Client-Version (notice the X prefix,
29
+ # capitalization and hyphens)
30
+ #
31
+ # Use the [] syntax to allocate subresources:
32
+ #
33
+ # site = RestClient::Resource.new('http://example.com', :user => 'adam', :password => 'mypasswd')
34
+ # site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
35
+ #
36
+ class Resource
37
+ attr_reader :url, :options, :block
38
+
39
+ def initialize(url, options={}, backwards_compatibility=nil, &block)
40
+ @url = url
41
+ @block = block
42
+ if options.class == Hash
43
+ @options = options
44
+ else # compatibility with previous versions
45
+ @options = { :user => options, :password => backwards_compatibility }
46
+ end
47
+ end
48
+
49
+ def get(additional_headers={}, &block)
50
+ headers = (options[:headers] || {}).merge(additional_headers)
51
+ Request.execute(options.merge(
52
+ :method => :get,
53
+ :url => url,
54
+ :headers => headers), &(block || @block))
55
+ end
56
+
57
+ def post(payload, additional_headers={}, &block)
58
+ headers = (options[:headers] || {}).merge(additional_headers)
59
+ Request.execute(options.merge(
60
+ :method => :post,
61
+ :url => url,
62
+ :payload => payload,
63
+ :headers => headers), &(block || @block))
64
+ end
65
+
66
+ def put(payload, additional_headers={}, &block)
67
+ headers = (options[:headers] || {}).merge(additional_headers)
68
+ Request.execute(options.merge(
69
+ :method => :put,
70
+ :url => url,
71
+ :payload => payload,
72
+ :headers => headers), &(block || @block))
73
+ end
74
+
75
+ def delete(additional_headers={}, &block)
76
+ headers = (options[:headers] || {}).merge(additional_headers)
77
+ Request.execute(options.merge(
78
+ :method => :delete,
79
+ :url => url,
80
+ :headers => headers), &(block || @block))
81
+ end
82
+
83
+ def to_s
84
+ url
85
+ end
86
+
87
+ def user
88
+ options[:user]
89
+ end
90
+
91
+ def password
92
+ options[:password]
93
+ end
94
+
95
+ def headers
96
+ options[:headers] || {}
97
+ end
98
+
99
+ def timeout
100
+ options[:timeout]
101
+ end
102
+
103
+ def open_timeout
104
+ options[:open_timeout]
105
+ end
106
+
107
+ # Construct a subresource, preserving authentication.
108
+ #
109
+ # Example:
110
+ #
111
+ # site = RestClient::Resource.new('http://example.com', 'adam', 'mypasswd')
112
+ # site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
113
+ #
114
+ # This is especially useful if you wish to define your site in one place and
115
+ # call it in multiple locations:
116
+ #
117
+ # def orders
118
+ # RestClient::Resource.new('http://example.com/orders', 'admin', 'mypasswd')
119
+ # end
120
+ #
121
+ # orders.get # GET http://example.com/orders
122
+ # orders['1'].get # GET http://example.com/orders/1
123
+ # orders['1/items'].delete # DELETE http://example.com/orders/1/items
124
+ #
125
+ # Nest resources as far as you want:
126
+ #
127
+ # site = RestClient::Resource.new('http://example.com')
128
+ # posts = site['posts']
129
+ # first_post = posts['1']
130
+ # comments = first_post['comments']
131
+ # comments.post 'Hello', :content_type => 'text/plain'
132
+ #
133
+ def [](suburl)
134
+ self.class.new(concat_urls(url, suburl), options)
135
+ end
136
+
137
+ def concat_urls(url, suburl) # :nodoc:
138
+ url = url.to_s
139
+ suburl = suburl.to_s
140
+ if url.slice(-1, 1) == '/' or suburl.slice(0, 1) == '/'
141
+ url + suburl
142
+ else
143
+ "#{url}/#{suburl}"
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,24 @@
1
+ module RestClient
2
+
3
+ # A Response from RestClient, you can access the response body, the code or the headers.
4
+ #
5
+ module Response
6
+
7
+ include AbstractResponse
8
+
9
+ attr_accessor :args, :body, :net_http_res
10
+
11
+ def body
12
+ self
13
+ end
14
+
15
+ def Response.create body, net_http_res, args
16
+ result = body || ''
17
+ result.extend Response
18
+ result.net_http_res = net_http_res
19
+ result.args = args
20
+ result
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,67 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ describe RestClient::AbstractResponse do
4
+
5
+ class MyAbstractResponse
6
+
7
+ include RestClient::AbstractResponse
8
+
9
+ attr_accessor :size
10
+
11
+ def initialize net_http_res, args
12
+ @net_http_res = net_http_res
13
+ @args = args
14
+ end
15
+
16
+ end
17
+
18
+ before do
19
+ @net_http_res = mock('net http response')
20
+ @response = MyAbstractResponse.new(@net_http_res, {})
21
+ end
22
+
23
+ it "fetches the numeric response code" do
24
+ @net_http_res.should_receive(:code).and_return('200')
25
+ @response.code.should == 200
26
+ end
27
+
28
+ it "has a nice description" do
29
+ @net_http_res.should_receive(:to_hash).and_return({'Content-Type' => ['application/pdf']})
30
+ @net_http_res.should_receive(:code).and_return('200')
31
+ @response.description == '200 OK | application/pdf bytes\n'
32
+ end
33
+
34
+ it "beautifies the headers by turning the keys to symbols" do
35
+ h = RestClient::AbstractResponse.beautify_headers('content-type' => [ 'x' ])
36
+ h.keys.first.should == :content_type
37
+ end
38
+
39
+ it "beautifies the headers by turning the values to strings instead of one-element arrays" do
40
+ h = RestClient::AbstractResponse.beautify_headers('x' => [ 'text/html' ] )
41
+ h.values.first.should == 'text/html'
42
+ end
43
+
44
+ it "fetches the headers" do
45
+ @net_http_res.should_receive(:to_hash).and_return('content-type' => [ 'text/html' ])
46
+ @response.headers.should == { :content_type => 'text/html' }
47
+ end
48
+
49
+ it "extracts cookies from response headers" do
50
+ @net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=1; path=/'])
51
+ @response.cookies.should == { 'session_id' => '1' }
52
+ end
53
+
54
+ it "extract strange cookies" do
55
+ @net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=ZJ/HQVH6YE+rVkTpn0zvTQ==; path=/'])
56
+ @response.cookies.should == { 'session_id' => 'ZJ%2FHQVH6YE+rVkTpn0zvTQ%3D%3D' }
57
+ end
58
+
59
+ it "doesn't escape cookies" do
60
+ @net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=BAh7BzoNYXBwX25hbWUiEGFwcGxpY2F0aW9uOgpsb2dpbiIKYWRtaW4%3D%0A--08114ba654f17c04d20dcc5228ec672508f738ca; path=/'])
61
+ @response.cookies.should == { 'session_id' => 'BAh7BzoNYXBwX25hbWUiEGFwcGxpY2F0aW9uOgpsb2dpbiIKYWRtaW4%3D%0A--08114ba654f17c04d20dcc5228ec672508f738ca' }
62
+ end
63
+
64
+ it "can access the net http result directly" do
65
+ @response.net_http_res.should == @net_http_res
66
+ end
67
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ begin
5
+ require "ruby-debug"
6
+ rescue LoadError
7
+ # NOP, ignore
8
+ end
9
+
10
+ require File.dirname(__FILE__) + '/../lib/restclient'
@@ -0,0 +1,79 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ require 'webmock/rspec'
4
+ include WebMock
5
+
6
+ describe RestClient::Exception do
7
+ it "sets the exception message to ErrorMessage" do
8
+ RestClient::ResourceNotFound.new.message.should == 'Resource Not Found'
9
+ end
10
+
11
+ it "contains exceptions in RestClient" do
12
+ RestClient::Unauthorized.new.should be_a_kind_of(RestClient::Exception)
13
+ RestClient::ServerBrokeConnection.new.should be_a_kind_of(RestClient::Exception)
14
+ end
15
+ end
16
+
17
+ describe RestClient::RequestFailed do
18
+ before do
19
+ @response = mock('HTTP Response', :code => '502')
20
+ end
21
+
22
+ it "stores the http response on the exception" do
23
+ response = "response"
24
+ begin
25
+ raise RestClient::RequestFailed, response
26
+ rescue RestClient::RequestFailed => e
27
+ e.response.should == response
28
+ end
29
+ end
30
+
31
+ it "http_code convenience method for fetching the code as an integer" do
32
+ RestClient::RequestFailed.new(@response).http_code.should == 502
33
+ end
34
+
35
+ it "http_body convenience method for fetching the body (decoding when necessary)" do
36
+ RestClient::RequestFailed.new(@response).http_code.should == 502
37
+ RestClient::RequestFailed.new(@response).message.should == 'HTTP status code 502'
38
+ end
39
+
40
+ it "shows the status code in the message" do
41
+ RestClient::RequestFailed.new(@response).to_s.should match(/502/)
42
+ end
43
+ end
44
+
45
+ describe RestClient::ResourceNotFound do
46
+ it "also has the http response attached" do
47
+ response = "response"
48
+ begin
49
+ raise RestClient::ResourceNotFound, response
50
+ rescue RestClient::ResourceNotFound => e
51
+ e.response.should == response
52
+ end
53
+ end
54
+ end
55
+
56
+ describe "backwards compatibility" do
57
+ it "alias RestClient::Request::Redirect to RestClient::Redirect" do
58
+ RestClient::Request::Redirect.should == RestClient::Redirect
59
+ end
60
+
61
+ it "alias RestClient::Request::Unauthorized to RestClient::Unauthorized" do
62
+ RestClient::Request::Unauthorized.should == RestClient::Unauthorized
63
+ end
64
+
65
+ it "alias RestClient::Request::RequestFailed to RestClient::RequestFailed" do
66
+ RestClient::Request::RequestFailed.should == RestClient::RequestFailed
67
+ end
68
+
69
+ it "make the exception's response act like an Net::HTTPResponse" do
70
+ body = "body"
71
+ stub_request(:get, "www.example.com").to_return(:body => body, :status => 404)
72
+ begin
73
+ RestClient.get "www.example.com"
74
+ raise
75
+ rescue RestClient::ResourceNotFound => e
76
+ e.response.body.should == body
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,19 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
3
+ UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
4
+ dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
5
+ MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
6
+ dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
7
+ AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
8
+ BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
9
+ cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
10
+ AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
11
+ MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
12
+ aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
13
+ ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
14
+ IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
15
+ MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
16
+ A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
17
+ 7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
18
+ 1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
19
+ -----END CERTIFICATE-----
@@ -0,0 +1,14 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
3
+ A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
4
+ cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
5
+ MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
6
+ BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
7
+ YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
8
+ ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
9
+ BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
10
+ I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
11
+ CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
12
+ lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
13
+ AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
14
+ -----END CERTIFICATE-----
@@ -0,0 +1,25 @@
1
+ require File.dirname(__FILE__) + '/../base'
2
+
3
+ describe RestClient::Request do
4
+ describe "ssl verification" do
5
+ it "is successful with the correct ca_file" do
6
+ request = RestClient::Request.new(
7
+ :method => :get,
8
+ :url => 'https://www.google.com',
9
+ :verify_ssl => OpenSSL::SSL::VERIFY_PEER,
10
+ :ssl_ca_file => File.join(File.dirname(__FILE__), "certs", "verisign.crt")
11
+ )
12
+ expect { request.execute }.to_not raise_error
13
+ end
14
+
15
+ it "is unsuccessful with an incorrect ca_file" do
16
+ request = RestClient::Request.new(
17
+ :method => :get,
18
+ :url => 'https://www.google.com',
19
+ :verify_ssl => OpenSSL::SSL::VERIFY_PEER,
20
+ :ssl_ca_file => File.join(File.dirname(__FILE__), "certs", "equifax.crt")
21
+ )
22
+ expect { request.execute }.to raise_error(RestClient::SSLCertificateNotVerified)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ require 'webmock/rspec'
4
+ include WebMock
5
+
6
+ describe RestClient do
7
+
8
+ it "a simple request" do
9
+ body = 'abc'
10
+ stub_request(:get, "www.example.com").to_return(:body => body, :status => 200)
11
+ response = RestClient.get "www.example.com"
12
+ response.code.should == 200
13
+ response.body.should == body
14
+ end
15
+
16
+ it "a simple request with gzipped content" do
17
+ stub_request(:get, "www.example.com").with(:headers => { 'Accept-Encoding' => 'gzip, deflate' }).to_return(:body => "\037\213\b\b\006'\252H\000\003t\000\313T\317UH\257\312,HM\341\002\000G\242(\r\v\000\000\000", :status => 200, :headers => { 'Content-Encoding' => 'gzip' } )
18
+ response = RestClient.get "www.example.com"
19
+ response.code.should == 200
20
+ response.body.should == "i'm gziped\n"
21
+ end
22
+
23
+ it "a 404" do
24
+ body = "Ho hai ! I'm not here !"
25
+ stub_request(:get, "www.example.com").to_return(:body => body, :status => 404)
26
+ begin
27
+ RestClient.get "www.example.com"
28
+ raise
29
+ rescue RestClient::ResourceNotFound => e
30
+ e.http_code.should == 404
31
+ e.response.code.should == 404
32
+ e.response.body.should == body
33
+ e.http_body.should == body
34
+ end
35
+ end
36
+
37
+
38
+ end
Binary file
@@ -0,0 +1,219 @@
1
+ require File.dirname(__FILE__) + "/base"
2
+
3
+ describe RestClient::Payload do
4
+ context "A regular Payload" do
5
+ it "should use standard enctype as default content-type" do
6
+ RestClient::Payload::UrlEncoded.new({}).headers['Content-Type'].
7
+ should == 'application/x-www-form-urlencoded'
8
+ end
9
+
10
+ it "should form properly encoded params" do
11
+ RestClient::Payload::UrlEncoded.new({:foo => 'bar'}).to_s.
12
+ should == "foo=bar"
13
+ ["foo=bar&baz=qux", "baz=qux&foo=bar"].should include(
14
+ RestClient::Payload::UrlEncoded.new({:foo => 'bar', :baz => 'qux'}).to_s)
15
+ end
16
+
17
+ it "should escape parameters" do
18
+ RestClient::Payload::UrlEncoded.new({'foo ' => 'bar'}).to_s.
19
+ should == "foo%20=bar"
20
+ end
21
+
22
+ it "should properly handle hashes as parameter" do
23
+ RestClient::Payload::UrlEncoded.new({:foo => {:bar => 'baz' }}).to_s.
24
+ should == "foo[bar]=baz"
25
+ RestClient::Payload::UrlEncoded.new({:foo => {:bar => {:baz => 'qux' }}}).to_s.
26
+ should == "foo[bar][baz]=qux"
27
+ end
28
+
29
+ it "should handle many attributes inside a hash" do
30
+ parameters = RestClient::Payload::UrlEncoded.new({:foo => {:bar => 'baz', :baz => 'qux'}}).to_s
31
+ parameters.should include("foo[bar]=baz", "foo[baz]=qux")
32
+ end
33
+
34
+ it "should handle attributes inside a an array inside an hash" do
35
+ parameters = RestClient::Payload::UrlEncoded.new({"foo" => [{"bar" => 'baz'}, {"bar" => 'qux'}]}).to_s
36
+ parameters.should include("foo[bar]=baz", "foo[bar]=qux")
37
+ end
38
+
39
+ it "should handle attributes inside a an array inside an array inside an hash" do
40
+ parameters = RestClient::Payload::UrlEncoded.new({"foo" => [ [{"bar" => 'baz'}, {"bar" => 'qux'}]]}).to_s
41
+ parameters.should include("foo[bar]=baz", "foo[bar]=qux")
42
+ end
43
+
44
+ it "should form properly use symbols as parameters" do
45
+ RestClient::Payload::UrlEncoded.new({:foo => :bar}).to_s.
46
+ should == "foo=bar"
47
+ RestClient::Payload::UrlEncoded.new({:foo => {:bar => :baz }}).to_s.
48
+ should == "foo[bar]=baz"
49
+ end
50
+
51
+ it "should properly handle arrays as repeated parameters" do
52
+ RestClient::Payload::UrlEncoded.new({:foo => ['bar']}).to_s.
53
+ should == "foo[]=bar"
54
+ RestClient::Payload::UrlEncoded.new({:foo => ['bar', 'baz']}).to_s.
55
+ should == "foo[]=bar&foo[]=baz"
56
+ end
57
+
58
+ end
59
+
60
+ context "A multipart Payload" do
61
+ it "should use standard enctype as default content-type" do
62
+ m = RestClient::Payload::Multipart.new({})
63
+ m.stub!(:boundary).and_return(123)
64
+ m.headers['Content-Type'].should == 'multipart/form-data; boundary=123'
65
+ end
66
+
67
+ it "should form properly separated multipart data" do
68
+ m = RestClient::Payload::Multipart.new([[:bar, "baz"], [:foo, "bar"]])
69
+ m.to_s.should == <<-EOS
70
+ --#{m.boundary}\r
71
+ Content-Disposition: form-data; name="bar"\r
72
+ \r
73
+ baz\r
74
+ --#{m.boundary}\r
75
+ Content-Disposition: form-data; name="foo"\r
76
+ \r
77
+ bar\r
78
+ --#{m.boundary}--\r
79
+ EOS
80
+ end
81
+
82
+ it "should not escape parameters names" do
83
+ m = RestClient::Payload::Multipart.new([["bar ", "baz"]])
84
+ m.to_s.should == <<-EOS
85
+ --#{m.boundary}\r
86
+ Content-Disposition: form-data; name="bar "\r
87
+ \r
88
+ baz\r
89
+ --#{m.boundary}--\r
90
+ EOS
91
+ end
92
+
93
+ it "should form properly separated multipart data" do
94
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
95
+ m = RestClient::Payload::Multipart.new({:foo => f})
96
+ m.to_s.should == <<-EOS
97
+ --#{m.boundary}\r
98
+ Content-Disposition: form-data; name="foo"; filename="master_shake.jpg"\r
99
+ Content-Type: image/jpeg\r
100
+ \r
101
+ #{IO.read(f.path)}\r
102
+ --#{m.boundary}--\r
103
+ EOS
104
+ end
105
+
106
+ it "should ignore the name attribute when it's not set" do
107
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
108
+ m = RestClient::Payload::Multipart.new({nil => f})
109
+ m.to_s.should == <<-EOS
110
+ --#{m.boundary}\r
111
+ Content-Disposition: form-data; filename="master_shake.jpg"\r
112
+ Content-Type: image/jpeg\r
113
+ \r
114
+ #{IO.read(f.path)}\r
115
+ --#{m.boundary}--\r
116
+ EOS
117
+ end
118
+
119
+ it "should detect optional (original) content type and filename" do
120
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
121
+ f.instance_eval "def content_type; 'text/plain'; end"
122
+ f.instance_eval "def original_filename; 'foo.txt'; end"
123
+ m = RestClient::Payload::Multipart.new({:foo => f})
124
+ m.to_s.should == <<-EOS
125
+ --#{m.boundary}\r
126
+ Content-Disposition: form-data; name="foo"; filename="foo.txt"\r
127
+ Content-Type: text/plain\r
128
+ \r
129
+ #{IO.read(f.path)}\r
130
+ --#{m.boundary}--\r
131
+ EOS
132
+ end
133
+
134
+ it "should handle hash in hash parameters" do
135
+ m = RestClient::Payload::Multipart.new({:bar => {:baz => "foo"}})
136
+ m.to_s.should == <<-EOS
137
+ --#{m.boundary}\r
138
+ Content-Disposition: form-data; name="bar[baz]"\r
139
+ \r
140
+ foo\r
141
+ --#{m.boundary}--\r
142
+ EOS
143
+
144
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
145
+ f.instance_eval "def content_type; 'text/plain'; end"
146
+ f.instance_eval "def original_filename; 'foo.txt'; end"
147
+ m = RestClient::Payload::Multipart.new({:foo => {:bar => f}})
148
+ m.to_s.should == <<-EOS
149
+ --#{m.boundary}\r
150
+ Content-Disposition: form-data; name="foo[bar]"; filename="foo.txt"\r
151
+ Content-Type: text/plain\r
152
+ \r
153
+ #{IO.read(f.path)}\r
154
+ --#{m.boundary}--\r
155
+ EOS
156
+ end
157
+
158
+ end
159
+
160
+ context "streamed payloads" do
161
+ it "should properly determine the size of file payloads" do
162
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
163
+ payload = RestClient::Payload.generate(f)
164
+ payload.size.should == 22_545
165
+ payload.length.should == 22_545
166
+ end
167
+
168
+ it "should properly determine the size of other kinds of streaming payloads" do
169
+ s = StringIO.new 'foo'
170
+ payload = RestClient::Payload.generate(s)
171
+ payload.size.should == 3
172
+ payload.length.should == 3
173
+
174
+ begin
175
+ f = Tempfile.new "rest-client"
176
+ f.write 'foo bar'
177
+
178
+ payload = RestClient::Payload.generate(f)
179
+ payload.size.should == 7
180
+ payload.length.should == 7
181
+ ensure
182
+ f.close
183
+ end
184
+ end
185
+ end
186
+
187
+ context "Payload generation" do
188
+ it "should recognize standard urlencoded params" do
189
+ RestClient::Payload.generate({"foo" => 'bar'}).should be_kind_of(RestClient::Payload::UrlEncoded)
190
+ end
191
+
192
+ it "should recognize multipart params" do
193
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
194
+ RestClient::Payload.generate({"foo" => f}).should be_kind_of(RestClient::Payload::Multipart)
195
+ end
196
+
197
+ it "should be multipart if forced" do
198
+ RestClient::Payload.generate({"foo" => "bar", :multipart => true}).should be_kind_of(RestClient::Payload::Multipart)
199
+ end
200
+
201
+ it "should return data if no of the above" do
202
+ RestClient::Payload.generate("data").should be_kind_of(RestClient::Payload::Base)
203
+ end
204
+
205
+ it "should recognize nested multipart payloads" do
206
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
207
+ RestClient::Payload.generate({"foo" => {"file" => f}}).should be_kind_of(RestClient::Payload::Multipart)
208
+ end
209
+
210
+ it "should recognize file payloads that can be streamed" do
211
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
212
+ RestClient::Payload.generate(f).should be_kind_of(RestClient::Payload::Streamed)
213
+ end
214
+
215
+ it "should recognize other payloads that can be streamed" do
216
+ RestClient::Payload.generate(StringIO.new('foo')).should be_kind_of(RestClient::Payload::Streamed)
217
+ end
218
+ end
219
+ end