rest-client-maestro 1.7.2.maestro

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 (46) hide show
  1. data/.gitignore +8 -0
  2. data/.rspec +1 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +8 -0
  5. data/AUTHORS +69 -0
  6. data/Gemfile +7 -0
  7. data/README.rdoc +322 -0
  8. data/Rakefile +49 -0
  9. data/bin/restclient +93 -0
  10. data/history.md +134 -0
  11. data/lib/rest-client.rb +2 -0
  12. data/lib/rest_client.rb +2 -0
  13. data/lib/restclient/abstract_response.rb +106 -0
  14. data/lib/restclient/exceptions.rb +198 -0
  15. data/lib/restclient/net_http_ext.rb +55 -0
  16. data/lib/restclient/payload.rb +242 -0
  17. data/lib/restclient/raw_response.rb +34 -0
  18. data/lib/restclient/request.rb +346 -0
  19. data/lib/restclient/resource.rb +169 -0
  20. data/lib/restclient/response.rb +26 -0
  21. data/lib/restclient.rb +174 -0
  22. data/rest-client-maestro.gemspec +23 -0
  23. data/spec/integration/capath_equifax/578d5c04.0 +19 -0
  24. data/spec/integration/capath_equifax/594f1775.0 +19 -0
  25. data/spec/integration/capath_equifax/README +8 -0
  26. data/spec/integration/capath_equifax/equifax.crt +19 -0
  27. data/spec/integration/capath_verisign/415660c1.0 +14 -0
  28. data/spec/integration/capath_verisign/7651b327.0 +14 -0
  29. data/spec/integration/capath_verisign/README +8 -0
  30. data/spec/integration/capath_verisign/verisign.crt +14 -0
  31. data/spec/integration/certs/equifax.crt +19 -0
  32. data/spec/integration/certs/verisign.crt +14 -0
  33. data/spec/integration/integration_spec.rb +35 -0
  34. data/spec/integration/request_spec.rb +63 -0
  35. data/spec/spec_helper.rb +12 -0
  36. data/spec/unit/abstract_response_spec.rb +85 -0
  37. data/spec/unit/exceptions_spec.rb +95 -0
  38. data/spec/unit/master_shake.jpg +0 -0
  39. data/spec/unit/payload_spec.rb +245 -0
  40. data/spec/unit/raw_response_spec.rb +17 -0
  41. data/spec/unit/request2_spec.rb +32 -0
  42. data/spec/unit/request_spec.rb +621 -0
  43. data/spec/unit/resource_spec.rb +133 -0
  44. data/spec/unit/response_spec.rb +166 -0
  45. data/spec/unit/restclient_spec.rb +73 -0
  46. metadata +220 -0
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
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 = double('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 eq 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.should eq "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 eq :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 eq '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 eq({ :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 eq({ '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 eq({ '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 eq({ '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 eq @net_http_res
66
+ end
67
+
68
+ describe "#return!" do
69
+ it "should return the response itself on 200-codes" do
70
+ @net_http_res.should_receive(:code).and_return('200')
71
+ @response.return!.should be_equal(@response)
72
+ end
73
+
74
+ it "should raise RequestFailed on unknown codes" do
75
+ @net_http_res.should_receive(:code).and_return('1000')
76
+ lambda { @response.return! }.should raise_error RestClient::RequestFailed
77
+ end
78
+
79
+ it "should raise an error on a redirection after non-GET/HEAD requests" do
80
+ @net_http_res.should_receive(:code).and_return('301')
81
+ @response.args.merge(:method => :put)
82
+ lambda { @response.return! }.should raise_error RestClient::RequestFailed
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ describe RestClient::Exception do
4
+ it "returns a 'message' equal to the class name if the message is not set, because 'message' should not be nil" do
5
+ e = RestClient::Exception.new
6
+ e.message.should eq "RestClient::Exception"
7
+ end
8
+
9
+ it "returns the 'message' that was set" do
10
+ e = RestClient::Exception.new
11
+ message = "An explicitly set message"
12
+ e.message = message
13
+ e.message.should eq message
14
+ end
15
+
16
+ it "sets the exception message to ErrorMessage" do
17
+ RestClient::ResourceNotFound.new.message.should eq 'Resource Not Found'
18
+ end
19
+
20
+ it "contains exceptions in RestClient" do
21
+ RestClient::Unauthorized.new.should be_a_kind_of(RestClient::Exception)
22
+ RestClient::ServerBrokeConnection.new.should be_a_kind_of(RestClient::Exception)
23
+ end
24
+ end
25
+
26
+ describe RestClient::ServerBrokeConnection do
27
+ it "should have a default message of 'Server broke connection'" do
28
+ e = RestClient::ServerBrokeConnection.new
29
+ e.message.should eq 'Server broke connection'
30
+ end
31
+ end
32
+
33
+ describe RestClient::RequestFailed do
34
+ before do
35
+ @response = double('HTTP Response', :code => '502')
36
+ end
37
+
38
+ it "stores the http response on the exception" do
39
+ response = "response"
40
+ begin
41
+ raise RestClient::RequestFailed, response
42
+ rescue RestClient::RequestFailed => e
43
+ e.response.should eq response
44
+ end
45
+ end
46
+
47
+ it "http_code convenience method for fetching the code as an integer" do
48
+ RestClient::RequestFailed.new(@response).http_code.should eq 502
49
+ end
50
+
51
+ it "http_body convenience method for fetching the body (decoding when necessary)" do
52
+ RestClient::RequestFailed.new(@response).http_code.should eq 502
53
+ RestClient::RequestFailed.new(@response).message.should eq 'HTTP status code 502'
54
+ end
55
+
56
+ it "shows the status code in the message" do
57
+ RestClient::RequestFailed.new(@response).to_s.should match(/502/)
58
+ end
59
+ end
60
+
61
+ describe RestClient::ResourceNotFound do
62
+ it "also has the http response attached" do
63
+ response = "response"
64
+ begin
65
+ raise RestClient::ResourceNotFound, response
66
+ rescue RestClient::ResourceNotFound => e
67
+ e.response.should eq response
68
+ end
69
+ end
70
+ end
71
+
72
+ describe "backwards compatibility" do
73
+ it "alias RestClient::Request::Redirect to RestClient::Redirect" do
74
+ RestClient::Request::Redirect.should eq RestClient::Redirect
75
+ end
76
+
77
+ it "alias RestClient::Request::Unauthorized to RestClient::Unauthorized" do
78
+ RestClient::Request::Unauthorized.should eq RestClient::Unauthorized
79
+ end
80
+
81
+ it "alias RestClient::Request::RequestFailed to RestClient::RequestFailed" do
82
+ RestClient::Request::RequestFailed.should eq RestClient::RequestFailed
83
+ end
84
+
85
+ it "make the exception's response act like an Net::HTTPResponse" do
86
+ body = "body"
87
+ stub_request(:get, "www.example.com").to_return(:body => body, :status => 404)
88
+ begin
89
+ RestClient.get "www.example.com"
90
+ raise
91
+ rescue RestClient::ResourceNotFound => e
92
+ e.response.body.should eq body
93
+ end
94
+ end
95
+ end
Binary file
@@ -0,0 +1,245 @@
1
+ # encoding: binary
2
+
3
+ require 'spec_helper'
4
+
5
+ describe RestClient::Payload do
6
+ context "A regular Payload" do
7
+ it "should use standard enctype as default content-type" do
8
+ RestClient::Payload::UrlEncoded.new({}).headers['Content-Type'].
9
+ should eq 'application/x-www-form-urlencoded'
10
+ end
11
+
12
+ it "should form properly encoded params" do
13
+ RestClient::Payload::UrlEncoded.new({:foo => 'bar'}).to_s.
14
+ should eq "foo=bar"
15
+ ["foo=bar&baz=qux", "baz=qux&foo=bar"].should include(
16
+ RestClient::Payload::UrlEncoded.new({:foo => 'bar', :baz => 'qux'}).to_s)
17
+ end
18
+
19
+ it "should escape parameters" do
20
+ RestClient::Payload::UrlEncoded.new({'foo ' => 'bar'}).to_s.
21
+ should eq "foo%20=bar"
22
+ end
23
+
24
+ it "should properly handle hashes as parameter" do
25
+ RestClient::Payload::UrlEncoded.new({:foo => {:bar => 'baz'}}).to_s.
26
+ should eq "foo[bar]=baz"
27
+ RestClient::Payload::UrlEncoded.new({:foo => {:bar => {:baz => 'qux'}}}).to_s.
28
+ should eq "foo[bar][baz]=qux"
29
+ end
30
+
31
+ it "should handle many attributes inside a hash" do
32
+ parameters = RestClient::Payload::UrlEncoded.new({:foo => {:bar => 'baz', :baz => 'qux'}}).to_s
33
+ parameters.should include("foo[bar]=baz", "foo[baz]=qux")
34
+ end
35
+
36
+ it "should handle attributes inside a an array inside an hash" do
37
+ parameters = RestClient::Payload::UrlEncoded.new({"foo" => [{"bar" => 'baz'}, {"bar" => 'qux'}]}).to_s
38
+ parameters.should include("foo[bar]=baz", "foo[bar]=qux")
39
+ end
40
+
41
+ it "should handle attributes inside a an array inside an array inside an hash" do
42
+ parameters = RestClient::Payload::UrlEncoded.new({"foo" => [[{"bar" => 'baz'}, {"bar" => 'qux'}]]}).to_s
43
+ parameters.should include("foo[bar]=baz", "foo[bar]=qux")
44
+ end
45
+
46
+ it "should form properly use symbols as parameters" do
47
+ RestClient::Payload::UrlEncoded.new({:foo => :bar}).to_s.
48
+ should eq "foo=bar"
49
+ RestClient::Payload::UrlEncoded.new({:foo => {:bar => :baz}}).to_s.
50
+ should eq "foo[bar]=baz"
51
+ end
52
+
53
+ it "should properly handle arrays as repeated parameters" do
54
+ RestClient::Payload::UrlEncoded.new({:foo => ['bar']}).to_s.
55
+ should eq "foo[]=bar"
56
+ RestClient::Payload::UrlEncoded.new({:foo => ['bar', 'baz']}).to_s.
57
+ should eq "foo[]=bar&foo[]=baz"
58
+ end
59
+
60
+ it 'should not close if stream already closed' do
61
+ p = RestClient::Payload::UrlEncoded.new({'foo ' => 'bar'})
62
+ 3.times {p.close}
63
+ end
64
+
65
+ end
66
+
67
+ context "A multipart Payload" do
68
+ it "should use standard enctype as default content-type" do
69
+ m = RestClient::Payload::Multipart.new({})
70
+ m.stub(:boundary).and_return(123)
71
+ m.headers['Content-Type'].should eq 'multipart/form-data; boundary=123'
72
+ end
73
+
74
+ it 'should not error on close if stream already closed' do
75
+ m = RestClient::Payload::Multipart.new(:file => File.new(File.join(File.dirname(File.expand_path(__FILE__)), 'master_shake.jpg')))
76
+ 3.times {m.close}
77
+ end
78
+
79
+ it "should form properly separated multipart data" do
80
+ m = RestClient::Payload::Multipart.new([[:bar, "baz"], [:foo, "bar"]])
81
+ m.to_s.should eq <<-EOS
82
+ --#{m.boundary}\r
83
+ Content-Disposition: form-data; name="bar"\r
84
+ \r
85
+ baz\r
86
+ --#{m.boundary}\r
87
+ Content-Disposition: form-data; name="foo"\r
88
+ \r
89
+ bar\r
90
+ --#{m.boundary}--\r
91
+ EOS
92
+ end
93
+
94
+ it "should not escape parameters names" do
95
+ m = RestClient::Payload::Multipart.new([["bar ", "baz"]])
96
+ m.to_s.should eq <<-EOS
97
+ --#{m.boundary}\r
98
+ Content-Disposition: form-data; name="bar "\r
99
+ \r
100
+ baz\r
101
+ --#{m.boundary}--\r
102
+ EOS
103
+ end
104
+
105
+ it "should form properly separated multipart data" do
106
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
107
+ m = RestClient::Payload::Multipart.new({:foo => f})
108
+ m.to_s.should eq <<-EOS
109
+ --#{m.boundary}\r
110
+ Content-Disposition: form-data; name="foo"; filename="master_shake.jpg"\r
111
+ Content-Type: image/jpeg\r
112
+ \r
113
+ #{File.open(f.path, 'rb'){|bin| bin.read}}\r
114
+ --#{m.boundary}--\r
115
+ EOS
116
+ end
117
+
118
+ it "should ignore the name attribute when it's not set" do
119
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
120
+ m = RestClient::Payload::Multipart.new({nil => f})
121
+ m.to_s.should eq <<-EOS
122
+ --#{m.boundary}\r
123
+ Content-Disposition: form-data; filename="master_shake.jpg"\r
124
+ Content-Type: image/jpeg\r
125
+ \r
126
+ #{File.open(f.path, 'rb'){|bin| bin.read}}\r
127
+ --#{m.boundary}--\r
128
+ EOS
129
+ end
130
+
131
+ it "should detect optional (original) content type and filename" do
132
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
133
+ f.instance_eval "def content_type; 'text/plain'; end"
134
+ f.instance_eval "def original_filename; 'foo.txt'; end"
135
+ m = RestClient::Payload::Multipart.new({:foo => f})
136
+ m.to_s.should eq <<-EOS
137
+ --#{m.boundary}\r
138
+ Content-Disposition: form-data; name="foo"; filename="foo.txt"\r
139
+ Content-Type: text/plain\r
140
+ \r
141
+ #{File.open(f.path, 'rb'){|bin| bin.read}}\r
142
+ --#{m.boundary}--\r
143
+ EOS
144
+ end
145
+
146
+ it "should handle hash in hash parameters" do
147
+ m = RestClient::Payload::Multipart.new({:bar => {:baz => "foo"}})
148
+ m.to_s.should eq <<-EOS
149
+ --#{m.boundary}\r
150
+ Content-Disposition: form-data; name="bar[baz]"\r
151
+ \r
152
+ foo\r
153
+ --#{m.boundary}--\r
154
+ EOS
155
+
156
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
157
+ f.instance_eval "def content_type; 'text/plain'; end"
158
+ f.instance_eval "def original_filename; 'foo.txt'; end"
159
+ m = RestClient::Payload::Multipart.new({:foo => {:bar => f}})
160
+ m.to_s.should eq <<-EOS
161
+ --#{m.boundary}\r
162
+ Content-Disposition: form-data; name="foo[bar]"; filename="foo.txt"\r
163
+ Content-Type: text/plain\r
164
+ \r
165
+ #{File.open(f.path, 'rb'){|bin| bin.read}}\r
166
+ --#{m.boundary}--\r
167
+ EOS
168
+ end
169
+
170
+ end
171
+
172
+ context "streamed payloads" do
173
+ it "should properly determine the size of file payloads" do
174
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
175
+ payload = RestClient::Payload.generate(f)
176
+ payload.size.should eq 76_988
177
+ payload.length.should eq 76_988
178
+ end
179
+
180
+ it "should properly determine the size of other kinds of streaming payloads" do
181
+ s = StringIO.new 'foo'
182
+ payload = RestClient::Payload.generate(s)
183
+ payload.size.should eq 3
184
+ payload.length.should eq 3
185
+
186
+ begin
187
+ f = Tempfile.new "rest-client"
188
+ f.write 'foo bar'
189
+
190
+ payload = RestClient::Payload.generate(f)
191
+ payload.size.should eq 7
192
+ payload.length.should eq 7
193
+ ensure
194
+ f.close
195
+ end
196
+ end
197
+ end
198
+
199
+ context "Payload generation" do
200
+ it "should recognize standard urlencoded params" do
201
+ RestClient::Payload.generate({"foo" => 'bar'}).should be_kind_of(RestClient::Payload::UrlEncoded)
202
+ end
203
+
204
+ it "should recognize multipart params" do
205
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
206
+ RestClient::Payload.generate({"foo" => f}).should be_kind_of(RestClient::Payload::Multipart)
207
+ end
208
+
209
+ it "should be multipart if forced" do
210
+ RestClient::Payload.generate({"foo" => "bar", :multipart => true}).should be_kind_of(RestClient::Payload::Multipart)
211
+ end
212
+
213
+ it "should return data if no of the above" do
214
+ RestClient::Payload.generate("data").should be_kind_of(RestClient::Payload::Base)
215
+ end
216
+
217
+ it "should recognize nested multipart payloads in hashes" do
218
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
219
+ RestClient::Payload.generate({"foo" => {"file" => f}}).should be_kind_of(RestClient::Payload::Multipart)
220
+ end
221
+
222
+ it "should recognize nested multipart payloads in arrays" do
223
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
224
+ RestClient::Payload.generate({"foo" => [f]}).should be_kind_of(RestClient::Payload::Multipart)
225
+ end
226
+
227
+ it "should recognize file payloads that can be streamed" do
228
+ f = File.new(File.dirname(__FILE__) + "/master_shake.jpg")
229
+ RestClient::Payload.generate(f).should be_kind_of(RestClient::Payload::Streamed)
230
+ end
231
+
232
+ it "should recognize other payloads that can be streamed" do
233
+ RestClient::Payload.generate(StringIO.new('foo')).should be_kind_of(RestClient::Payload::Streamed)
234
+ end
235
+
236
+ # hashery gem introduces Hash#read convenience method. Existence of #read method used to determine of content is streameable :/
237
+ it "shouldn't treat hashes as streameable" do
238
+ RestClient::Payload.generate({"foo" => 'bar'}).should be_kind_of(RestClient::Payload::UrlEncoded)
239
+ end
240
+ end
241
+
242
+ class HashMapForTesting < Hash
243
+ alias :read :[]
244
+ end
245
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe RestClient::RawResponse do
4
+ before do
5
+ @tf = double("Tempfile", :read => "the answer is 42", :open => true)
6
+ @net_http_res = double('net http response')
7
+ @response = RestClient::RawResponse.new(@tf, @net_http_res, {})
8
+ end
9
+
10
+ it "behaves like string" do
11
+ @response.to_s.should eq 'the answer is 42'
12
+ end
13
+
14
+ it "exposes a Tempfile" do
15
+ @response.file.should eq @tf
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe RestClient::Request do
4
+
5
+ it "manage params for get requests" do
6
+ stub_request(:get, 'http://some/resource?a=b&c=d').with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate', 'Foo'=>'bar'}).to_return(:body => 'foo', :status => 200)
7
+ RestClient::Request.execute(:url => 'http://some/resource', :method => :get, :headers => {:foo => :bar, :params => {:a => :b, 'c' => 'd'}}).body.should eq 'foo'
8
+
9
+ stub_request(:get, 'http://some/resource').with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate', 'Foo'=>'bar', 'params' => 'a'}).to_return(:body => 'foo', :status => 200)
10
+ RestClient::Request.execute(:url => 'http://some/resource', :method => :get, :headers => {:foo => :bar, :params => :a}).body.should eq 'foo'
11
+ end
12
+
13
+ it "can use a block to process response" do
14
+ response_value = nil
15
+ block = Proc.new do |http_response|
16
+ response_value = http_response.body
17
+ end
18
+ stub_request(:get, 'http://some/resource?a=b&c=d').with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate', 'Foo'=>'bar'}).to_return(:body => 'foo', :status => 200)
19
+ RestClient::Request.execute(:url => 'http://some/resource', :method => :get, :headers => {:foo => :bar, :params => {:a => :b, 'c' => 'd'}}, :block_response => block)
20
+ response_value.should eq "foo"
21
+ end
22
+
23
+ it 'closes payload if not nil' do
24
+ test_file = File.new(File.join( File.dirname(File.expand_path(__FILE__)), 'master_shake.jpg'))
25
+
26
+ stub_request(:post, 'http://some/resource').with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).to_return(:body => 'foo', :status => 200)
27
+ RestClient::Request.execute(:url => 'http://some/resource', :method => :post, :payload => {:file => test_file})
28
+
29
+ test_file.closed?.should be_true
30
+ end
31
+
32
+ end