right_agent 2.0.7-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +82 -0
  3. data/Rakefile +113 -0
  4. data/lib/right_agent.rb +59 -0
  5. data/lib/right_agent/actor.rb +182 -0
  6. data/lib/right_agent/actor_registry.rb +76 -0
  7. data/lib/right_agent/actors/agent_manager.rb +232 -0
  8. data/lib/right_agent/agent.rb +1149 -0
  9. data/lib/right_agent/agent_config.rb +480 -0
  10. data/lib/right_agent/agent_identity.rb +210 -0
  11. data/lib/right_agent/agent_tag_manager.rb +237 -0
  12. data/lib/right_agent/audit_formatter.rb +107 -0
  13. data/lib/right_agent/clients.rb +31 -0
  14. data/lib/right_agent/clients/api_client.rb +383 -0
  15. data/lib/right_agent/clients/auth_client.rb +247 -0
  16. data/lib/right_agent/clients/balanced_http_client.rb +369 -0
  17. data/lib/right_agent/clients/base_retry_client.rb +495 -0
  18. data/lib/right_agent/clients/right_http_client.rb +279 -0
  19. data/lib/right_agent/clients/router_client.rb +493 -0
  20. data/lib/right_agent/command.rb +30 -0
  21. data/lib/right_agent/command/agent_manager_commands.rb +150 -0
  22. data/lib/right_agent/command/command_client.rb +136 -0
  23. data/lib/right_agent/command/command_constants.rb +33 -0
  24. data/lib/right_agent/command/command_io.rb +126 -0
  25. data/lib/right_agent/command/command_parser.rb +87 -0
  26. data/lib/right_agent/command/command_runner.rb +118 -0
  27. data/lib/right_agent/command/command_serializer.rb +63 -0
  28. data/lib/right_agent/connectivity_checker.rb +179 -0
  29. data/lib/right_agent/console.rb +65 -0
  30. data/lib/right_agent/core_payload_types.rb +44 -0
  31. data/lib/right_agent/core_payload_types/cookbook.rb +61 -0
  32. data/lib/right_agent/core_payload_types/cookbook_position.rb +46 -0
  33. data/lib/right_agent/core_payload_types/cookbook_repository.rb +116 -0
  34. data/lib/right_agent/core_payload_types/cookbook_sequence.rb +70 -0
  35. data/lib/right_agent/core_payload_types/dev_repositories.rb +100 -0
  36. data/lib/right_agent/core_payload_types/dev_repository.rb +76 -0
  37. data/lib/right_agent/core_payload_types/event_categories.rb +38 -0
  38. data/lib/right_agent/core_payload_types/executable_bundle.rb +130 -0
  39. data/lib/right_agent/core_payload_types/login_policy.rb +72 -0
  40. data/lib/right_agent/core_payload_types/login_user.rb +79 -0
  41. data/lib/right_agent/core_payload_types/planned_volume.rb +94 -0
  42. data/lib/right_agent/core_payload_types/recipe_instantiation.rb +73 -0
  43. data/lib/right_agent/core_payload_types/repositories_bundle.rb +50 -0
  44. data/lib/right_agent/core_payload_types/right_script_attachment.rb +95 -0
  45. data/lib/right_agent/core_payload_types/right_script_instantiation.rb +94 -0
  46. data/lib/right_agent/core_payload_types/runlist_policy.rb +44 -0
  47. data/lib/right_agent/core_payload_types/secure_document.rb +66 -0
  48. data/lib/right_agent/core_payload_types/secure_document_location.rb +63 -0
  49. data/lib/right_agent/core_payload_types/software_repository_instantiation.rb +61 -0
  50. data/lib/right_agent/daemonize.rb +35 -0
  51. data/lib/right_agent/dispatched_cache.rb +109 -0
  52. data/lib/right_agent/dispatcher.rb +272 -0
  53. data/lib/right_agent/enrollment_result.rb +221 -0
  54. data/lib/right_agent/exceptions.rb +87 -0
  55. data/lib/right_agent/history.rb +145 -0
  56. data/lib/right_agent/log.rb +460 -0
  57. data/lib/right_agent/minimal.rb +46 -0
  58. data/lib/right_agent/monkey_patches.rb +30 -0
  59. data/lib/right_agent/monkey_patches/ruby_patch.rb +55 -0
  60. data/lib/right_agent/monkey_patches/ruby_patch/array_patch.rb +29 -0
  61. data/lib/right_agent/monkey_patches/ruby_patch/darwin_patch.rb +24 -0
  62. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch.rb +24 -0
  63. data/lib/right_agent/monkey_patches/ruby_patch/linux_patch/file_patch.rb +30 -0
  64. data/lib/right_agent/monkey_patches/ruby_patch/object_patch.rb +49 -0
  65. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch.rb +32 -0
  66. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/file_patch.rb +60 -0
  67. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/process_patch.rb +63 -0
  68. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/stdio_patch.rb +27 -0
  69. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/time_patch.rb +55 -0
  70. data/lib/right_agent/monkey_patches/ruby_patch/windows_patch/win32ole_patch.rb +34 -0
  71. data/lib/right_agent/multiplexer.rb +102 -0
  72. data/lib/right_agent/offline_handler.rb +270 -0
  73. data/lib/right_agent/operation_result.rb +300 -0
  74. data/lib/right_agent/packets.rb +673 -0
  75. data/lib/right_agent/payload_formatter.rb +104 -0
  76. data/lib/right_agent/pending_requests.rb +128 -0
  77. data/lib/right_agent/pid_file.rb +159 -0
  78. data/lib/right_agent/platform.rb +770 -0
  79. data/lib/right_agent/platform/unix/darwin/platform.rb +102 -0
  80. data/lib/right_agent/platform/unix/linux/platform.rb +305 -0
  81. data/lib/right_agent/platform/unix/platform.rb +226 -0
  82. data/lib/right_agent/platform/windows/mingw/platform.rb +447 -0
  83. data/lib/right_agent/platform/windows/mswin/platform.rb +236 -0
  84. data/lib/right_agent/platform/windows/platform.rb +1808 -0
  85. data/lib/right_agent/protocol_version_mixin.rb +69 -0
  86. data/lib/right_agent/retryable_request.rb +195 -0
  87. data/lib/right_agent/scripts/agent_controller.rb +543 -0
  88. data/lib/right_agent/scripts/agent_deployer.rb +400 -0
  89. data/lib/right_agent/scripts/common_parser.rb +160 -0
  90. data/lib/right_agent/scripts/log_level_manager.rb +192 -0
  91. data/lib/right_agent/scripts/stats_manager.rb +268 -0
  92. data/lib/right_agent/scripts/usage.rb +58 -0
  93. data/lib/right_agent/secure_identity.rb +92 -0
  94. data/lib/right_agent/security.rb +32 -0
  95. data/lib/right_agent/security/cached_certificate_store_proxy.rb +77 -0
  96. data/lib/right_agent/security/certificate.rb +102 -0
  97. data/lib/right_agent/security/certificate_cache.rb +89 -0
  98. data/lib/right_agent/security/distinguished_name.rb +56 -0
  99. data/lib/right_agent/security/encrypted_document.rb +83 -0
  100. data/lib/right_agent/security/rsa_key_pair.rb +76 -0
  101. data/lib/right_agent/security/signature.rb +86 -0
  102. data/lib/right_agent/security/static_certificate_store.rb +85 -0
  103. data/lib/right_agent/sender.rb +792 -0
  104. data/lib/right_agent/serialize.rb +29 -0
  105. data/lib/right_agent/serialize/message_pack.rb +107 -0
  106. data/lib/right_agent/serialize/secure_serializer.rb +151 -0
  107. data/lib/right_agent/serialize/secure_serializer_initializer.rb +47 -0
  108. data/lib/right_agent/serialize/serializable.rb +151 -0
  109. data/lib/right_agent/serialize/serializer.rb +159 -0
  110. data/lib/right_agent/subprocess.rb +38 -0
  111. data/lib/right_agent/tracer.rb +124 -0
  112. data/right_agent.gemspec +101 -0
  113. data/spec/actor_registry_spec.rb +80 -0
  114. data/spec/actor_spec.rb +162 -0
  115. data/spec/agent_config_spec.rb +235 -0
  116. data/spec/agent_identity_spec.rb +78 -0
  117. data/spec/agent_spec.rb +734 -0
  118. data/spec/agent_tag_manager_spec.rb +319 -0
  119. data/spec/clients/api_client_spec.rb +423 -0
  120. data/spec/clients/auth_client_spec.rb +272 -0
  121. data/spec/clients/balanced_http_client_spec.rb +576 -0
  122. data/spec/clients/base_retry_client_spec.rb +635 -0
  123. data/spec/clients/router_client_spec.rb +594 -0
  124. data/spec/clients/spec_helper.rb +111 -0
  125. data/spec/command/agent_manager_commands_spec.rb +51 -0
  126. data/spec/command/command_io_spec.rb +93 -0
  127. data/spec/command/command_parser_spec.rb +79 -0
  128. data/spec/command/command_runner_spec.rb +107 -0
  129. data/spec/command/command_serializer_spec.rb +51 -0
  130. data/spec/connectivity_checker_spec.rb +83 -0
  131. data/spec/core_payload_types/dev_repositories_spec.rb +64 -0
  132. data/spec/core_payload_types/dev_repository_spec.rb +33 -0
  133. data/spec/core_payload_types/executable_bundle_spec.rb +67 -0
  134. data/spec/core_payload_types/login_user_spec.rb +102 -0
  135. data/spec/core_payload_types/recipe_instantiation_spec.rb +81 -0
  136. data/spec/core_payload_types/right_script_attachment_spec.rb +65 -0
  137. data/spec/core_payload_types/right_script_instantiation_spec.rb +79 -0
  138. data/spec/core_payload_types/spec_helper.rb +23 -0
  139. data/spec/dispatched_cache_spec.rb +136 -0
  140. data/spec/dispatcher_spec.rb +324 -0
  141. data/spec/enrollment_result_spec.rb +53 -0
  142. data/spec/history_spec.rb +246 -0
  143. data/spec/log_spec.rb +192 -0
  144. data/spec/monkey_patches/eventmachine_spec.rb +62 -0
  145. data/spec/multiplexer_spec.rb +48 -0
  146. data/spec/offline_handler_spec.rb +340 -0
  147. data/spec/operation_result_spec.rb +208 -0
  148. data/spec/packets_spec.rb +461 -0
  149. data/spec/pending_requests_spec.rb +136 -0
  150. data/spec/platform/spec_helper.rb +216 -0
  151. data/spec/platform/unix/darwin/platform_spec.rb +181 -0
  152. data/spec/platform/unix/linux/platform_spec.rb +540 -0
  153. data/spec/platform/unix/spec_helper.rb +149 -0
  154. data/spec/platform/windows/mingw/platform_spec.rb +222 -0
  155. data/spec/platform/windows/mswin/platform_spec.rb +259 -0
  156. data/spec/platform/windows/spec_helper.rb +720 -0
  157. data/spec/retryable_request_spec.rb +306 -0
  158. data/spec/secure_identity_spec.rb +50 -0
  159. data/spec/security/cached_certificate_store_proxy_spec.rb +62 -0
  160. data/spec/security/certificate_cache_spec.rb +71 -0
  161. data/spec/security/certificate_spec.rb +49 -0
  162. data/spec/security/distinguished_name_spec.rb +46 -0
  163. data/spec/security/encrypted_document_spec.rb +55 -0
  164. data/spec/security/rsa_key_pair_spec.rb +55 -0
  165. data/spec/security/signature_spec.rb +66 -0
  166. data/spec/security/static_certificate_store_spec.rb +58 -0
  167. data/spec/sender_spec.rb +1045 -0
  168. data/spec/serialize/message_pack_spec.rb +131 -0
  169. data/spec/serialize/secure_serializer_spec.rb +132 -0
  170. data/spec/serialize/serializable_spec.rb +90 -0
  171. data/spec/serialize/serializer_spec.rb +197 -0
  172. data/spec/spec.opts +2 -0
  173. data/spec/spec.win32.opts +1 -0
  174. data/spec/spec_helper.rb +130 -0
  175. data/spec/tracer_spec.rb +114 -0
  176. metadata +447 -0
@@ -0,0 +1,272 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
25
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'right_agent', 'clients', 'auth_client'))
26
+
27
+ class AuthClientTest < RightScale::AuthClient
28
+ def initialize(options = {})
29
+ @identity = options[:identity]
30
+ @api_url = options[:api_url]
31
+ @router_url = options[:router_url]
32
+ @account_id = options[:account_id]
33
+ @mode = options[:mode]
34
+ @access_token = options[:access_token]
35
+ @state = :pending
36
+ @status_callbacks = []
37
+ reset_stats
38
+ end
39
+ end
40
+
41
+ describe RightScale::AuthClient do
42
+
43
+ include FlexMock::ArgumentTypes
44
+
45
+ before(:each) do
46
+ @log = flexmock(RightScale::Log)
47
+ @log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
48
+ @log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
49
+ @identity = "rs-agent-1-1"
50
+ @api_url = "http://api.com"
51
+ @router_url = "http://router.com"
52
+ @options = {
53
+ :identity => @identity,
54
+ :api_url => @api_url,
55
+ :router_url => @router_url,
56
+ :account_id => 123,
57
+ :mode => :http,
58
+ :access_token => "<test access token>" }
59
+ @client = AuthClientTest.new(@options)
60
+ end
61
+
62
+ context :initialize do
63
+ it "raises if abstract class" do
64
+ lambda { RightScale::AuthClient.new }.should raise_error(NotImplementedError, "RightScale::AuthClient is an abstract class")
65
+ end
66
+
67
+ it "does not raise for derived class" do
68
+ AuthClientTest.new
69
+ end
70
+ end
71
+
72
+ context :identity do
73
+ it "returns identity" do
74
+ @client.identity.should == @identity
75
+ end
76
+ end
77
+
78
+ context :headers do
79
+ it "raises if not authorized" do
80
+ lambda { @client.headers }.should raise_error(RightScale::Exceptions::Unauthorized)
81
+ end
82
+
83
+ it "returns headers" do
84
+ @client.send(:state=, :authorized)
85
+ @client.headers.should == {"Authorization" => "Bearer <test access token>"}
86
+ end
87
+ end
88
+
89
+ context :auth_header do
90
+ it "raises if not authorized" do
91
+ lambda { @client.auth_header }.should raise_error(RightScale::Exceptions::Unauthorized)
92
+ end
93
+
94
+ it "returns authorization header" do
95
+ @client.send(:state=, :authorized)
96
+ @client.auth_header.should == {"Authorization" => "Bearer <test access token>"}
97
+ end
98
+ end
99
+
100
+ context :account_id do
101
+ it "raises if not authorized" do
102
+ lambda { @client.account_id }.should raise_error(RightScale::Exceptions::Unauthorized)
103
+ end
104
+
105
+ it "returns auth header" do
106
+ @client.send(:state=, :authorized)
107
+ @client.account_id.should == 123
108
+ end
109
+ end
110
+
111
+ context :api_url do
112
+ it "raises if not authorized" do
113
+ lambda { @client.api_url }.should raise_error(RightScale::Exceptions::Unauthorized)
114
+ end
115
+
116
+ it "returns auth header" do
117
+ @client.send(:state=, :authorized)
118
+ @client.api_url.should == @api_url
119
+ end
120
+ end
121
+
122
+ context :router_url do
123
+ it "raises if not authorized" do
124
+ lambda { @client.router_url }.should raise_error(RightScale::Exceptions::Unauthorized)
125
+ end
126
+
127
+ it "returns auth header" do
128
+ @client.send(:state=, :authorized)
129
+ @client.router_url.should == @router_url
130
+ end
131
+ end
132
+
133
+ context :mode do
134
+ it "returns mode" do
135
+ @client.mode.should == :http
136
+ end
137
+ end
138
+
139
+ context :expired do
140
+ it "logs that authorization expired" do
141
+ @log.should_receive(:info).with(/Renewing authorization/).once
142
+ @client.send(:state=, :authorized)
143
+ @client.expired.should be_true
144
+ end
145
+
146
+ it "sets state to :expired" do
147
+ @client.send(:state=, :authorized)
148
+ @client.expired.should be_true
149
+ @client.state.should == :expired
150
+ end
151
+
152
+ it "should renew authorization" do
153
+ @client.send(:state=, :authorized)
154
+ flexmock(@client).should_receive(:renew_authorization).once
155
+ @client.expired.should be_true
156
+ end
157
+ end
158
+
159
+ context :redirect do
160
+ it "handles redirect" do
161
+ @client.redirect("location").should be_true
162
+ end
163
+ end
164
+
165
+ context :close do
166
+ it "should set state to :closed" do
167
+ @client.close.should be_true
168
+ @client.state.should == :closed
169
+ end
170
+ end
171
+
172
+ context :status do
173
+ it "stores callback" do
174
+ callback = lambda { |_, _| }
175
+ @client.instance_variable_get(:@status_callbacks).size.should == 0
176
+ @client.status(&callback)
177
+ @client.instance_variable_get(:@status_callbacks).size.should == 1
178
+ @client.instance_variable_get(:@status_callbacks)[0].should == callback
179
+ end
180
+
181
+ it "treats callback as optional" do
182
+ @client.instance_variable_get(:@status_callbacks).size.should == 0
183
+ @client.status
184
+ @client.instance_variable_get(:@status_callbacks).size.should == 0
185
+ end
186
+
187
+ it "returns current state" do
188
+ @client.status.should == :pending
189
+ end
190
+ end
191
+
192
+ context :check_authorized do
193
+ it "raises retryable error if state is :expired" do
194
+ @client.send(:state=, :authorized)
195
+ @client.send(:expired)
196
+ lambda { @client.send(:check_authorized) }.should raise_error(RightScale::Exceptions::RetryableError, "Authorization expired")
197
+ end
198
+
199
+ it "raises unauthorized if state is not :authorized" do
200
+ lambda { @client.send(:check_authorized) }.should raise_error(RightScale::Exceptions::Unauthorized, "Not authorized with RightScale")
201
+ end
202
+
203
+ it "returns true if authorized" do
204
+ @client.send(:state=, :authorized)
205
+ @client.send(:check_authorized).should be_true
206
+ end
207
+ end
208
+
209
+ context :renew_authorization do
210
+ it "renews authorization" do
211
+ @client.send(:renew_authorization).should be_true
212
+ end
213
+
214
+ it "waits to renew authorization" do
215
+ @client.send(:renew_authorization, 10).should be_true
216
+ end
217
+ end
218
+
219
+ context :state= do
220
+ it "raises exception if state transition is invalid" do
221
+ lambda { @client.send(:state=, :expired) }.should raise_error(ArgumentError, "Invalid state transition: :pending -> :expired")
222
+ end
223
+
224
+ [:pending, :closed].each do |state|
225
+ context state do
226
+ it "stores new state" do
227
+ @client.send(:state=, state)
228
+ @client.state.should == state
229
+ end
230
+ end
231
+ end
232
+
233
+ [:authorized, :unauthorized, :expired, :failed].each do |state|
234
+ context state do
235
+ before(:each) do
236
+ @client.send(:state=, :authorized) if state == :expired
237
+ end
238
+
239
+ it "stores new state" do
240
+ @client.send(:state=, state)
241
+ @client.state.should == state
242
+ end
243
+
244
+ it "stores new state" do
245
+ @client.send(:state=, state).should == state
246
+ end
247
+
248
+ context "when callbacks" do
249
+ it "makes callbacks with new state" do
250
+ callback_type = callback_state = nil
251
+ @client.status { |t, s| callback_type = t; callback_state = s }
252
+ @client.send(:state=, state)
253
+ callback_type.should == :auth
254
+ callback_state.should == state
255
+ end
256
+
257
+ it "log error if callback fails" do
258
+ @log.should_receive(:error).with("Failed status callback", StandardError).once
259
+ @client.status { |t, s| raise StandardError, "test" }
260
+ @client.send(:state=, state).should == state
261
+ end
262
+ end
263
+
264
+ it "does nothing if current state is the same" do
265
+ flexmock(@client.instance_variable_get(:@stats)["state"]).should_receive(:update).once
266
+ @client.send(:state=, state)
267
+ @client.send(:state=, state).should == state
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,576 @@
1
+ #--
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
25
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'right_agent', 'clients', 'balanced_http_client'))
26
+
27
+ describe RightScale::BalancedHttpClient do
28
+
29
+ include FlexMock::ArgumentTypes
30
+
31
+ before(:each) do
32
+ @log = flexmock(RightScale::Log)
33
+ @log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
34
+ @log.should_receive(:warning).by_default.and_return { |m| raise RightScale::Log.format(*m) }
35
+ @url = "http://my.com"
36
+ @urls = [@url]
37
+ @path = "/foo/bar"
38
+ @balancer = flexmock("balancer")
39
+ end
40
+
41
+ context :initialize do
42
+ ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy', 'ALL_PROXY'].each do |proxy|
43
+ it "initializes use of proxy if #{proxy} defined in environment" do
44
+ ENV[proxy] = "https://my.proxy.com"
45
+ flexmock(RightSupport::Net::RequestBalancer).should_receive(:new).and_return(@balancer)
46
+ flexmock(RestClient).should_receive(:proxy=).with("https://my.proxy.com").once
47
+ RightScale::BalancedHttpClient.new(@urls)
48
+ ENV.delete(proxy)
49
+ end
50
+
51
+ it "prepends scheme to proxy address if #{proxy} defined in environment" do
52
+ ENV[proxy] = "1.2.3.4"
53
+ flexmock(RightSupport::Net::RequestBalancer).should_receive(:new).and_return(@balancer)
54
+ flexmock(RestClient).should_receive(:proxy=).with("http://1.2.3.4").once
55
+ RightScale::BalancedHttpClient.new(@urls)
56
+ ENV.delete(proxy)
57
+ end
58
+ end
59
+
60
+ it "creates request balancer with health checker" do
61
+ flexmock(RightSupport::Net::RequestBalancer).should_receive(:new).
62
+ with(@urls, hsh(:policy => RightSupport::Net::LB::HealthCheck)).and_return(@balancer).once
63
+ RightScale::BalancedHttpClient.new(@urls)
64
+ end
65
+
66
+ it "accepts comma-separated list of URLs" do
67
+ flexmock(RightSupport::Net::RequestBalancer).should_receive(:new).
68
+ with(on { |arg| arg.should == ["url1", "url2"] }, Hash).and_return(@balancer).once
69
+ RightScale::BalancedHttpClient.new("url1, url2")
70
+ end
71
+ end
72
+
73
+ context :check_health do
74
+ before(:each) do
75
+ @urls = ["http://my0.com", @url]
76
+ @http_client = flexmock("http client")
77
+ flexmock(RightSupport::Net::HTTPClient).should_receive(:new).and_return(@http_client).by_default
78
+ @client = RightScale::BalancedHttpClient.new(@urls)
79
+ end
80
+
81
+ it "calls health check proc using first URL" do
82
+ @http_client.should_receive(:get).with("http://my0.com/health-check", Hash).once
83
+ @client.check_health
84
+ end
85
+
86
+ it "calls health check proc using specified URL" do
87
+ @http_client.should_receive(:get).with("http://my1.com/health-check", Hash).once
88
+ @client.check_health("http://my1.com")
89
+ end
90
+
91
+ RightScale::BalancedHttpClient::RETRY_STATUS_CODES.each do |code|
92
+ it "raises NotResponding for #{code}" do
93
+ @http_client.should_receive(:get).and_raise(RestExceptionMock.new(code))
94
+ lambda { @client.check_health("http://my1.com") }.should raise_error(RightScale::BalancedHttpClient::NotResponding)
95
+ end
96
+ end
97
+ end
98
+
99
+ context :request do
100
+ before(:each) do
101
+ @result = {:out => 123}
102
+ @json_result = JSON.dump(@result)
103
+ @json_decoded_result = {"out" => 123}
104
+ @response = flexmock("response")
105
+ @response.should_receive(:code).and_return(200).by_default
106
+ @response.should_receive(:body).and_return(@json_result).by_default
107
+ @response.should_receive(:headers).and_return({:status => "200 OK"}).by_default
108
+ flexmock(RightSupport::Data::UUID).should_receive(:generate).and_return("random uuid").by_default
109
+ @balancer.should_receive(:request).and_yield(@url).by_default
110
+ flexmock(RightSupport::Net::RequestBalancer).should_receive(:new).and_return(@balancer).by_default
111
+ @http_client = flexmock("http client")
112
+ flexmock(RightSupport::Net::HTTPClient).should_receive(:new).and_return(@http_client).by_default
113
+ @client = RightScale::BalancedHttpClient.new(@urls)
114
+ end
115
+
116
+ context "with no options" do
117
+ before(:each) do
118
+ @options = {}
119
+ @http_client.should_receive(:get).with("#{@url}#{@path}", on { |a| @options = a }).and_return(nil)
120
+ @client.send(:request, :get, @path).should be_nil
121
+ end
122
+
123
+ it "sets default open and request timeouts" do
124
+ @options[:open_timeout].should == RightScale::BalancedHttpClient::DEFAULT_OPEN_TIMEOUT
125
+ @options[:timeout].should == RightScale::BalancedHttpClient::DEFAULT_REQUEST_TIMEOUT
126
+ end
127
+
128
+ it "sets random request uuid in header" do
129
+ @options[:headers]["X-Request-Lineage-Uuid"] == "random uuid"
130
+ end
131
+
132
+ it "sets response to be JSON-encoded" do
133
+ @options[:headers][:accept] == "application/json"
134
+ end
135
+
136
+ it "does not set API version" do
137
+ @options[:headers]["X-API-Version"].should be_nil
138
+ end
139
+ end
140
+
141
+ context "with options" do
142
+ before(:each) do
143
+ @options = {}
144
+ create_options = {
145
+ :request_uuid => "my uuid",
146
+ :api_version => "1.0" }
147
+ request_options = {
148
+ :open_timeout => 1,
149
+ :request_timeout => 2,
150
+ :headers => {"Authorization" => "Bearer <session>"} }
151
+ @client = RightScale::BalancedHttpClient.new(@urls, create_options)
152
+ @http_client.should_receive(:get).with("#{@url}#{@path}", on { |a| @options = a }).and_return(nil)
153
+ @client.send(:request, :get, @path, nil, request_options).should be_nil
154
+ end
155
+
156
+ it "sets open and request timeouts" do
157
+ @options[:open_timeout].should == 1
158
+ @options[:timeout].should == 2
159
+ end
160
+
161
+ it "sets request uuid in header" do
162
+ @options[:headers]["X-Request-Lineage-Uuid"] == "my uuid"
163
+ end
164
+
165
+ it "sets API version in header" do
166
+ @options[:headers]["X-API-Version"].should == "1.0"
167
+ end
168
+
169
+ it "uses headers for setting authorization in header" do
170
+ @options[:headers]["Authorization"].should == "Bearer <session>"
171
+ end
172
+ end
173
+
174
+ [:get, :delete].each do |verb|
175
+ context "with #{verb.inspect}" do
176
+ it "uses form-encoded query option for parameters" do
177
+ params = {:id => 10}
178
+ @http_client.should_receive(verb).with("#{@url}#{@path}", on { |a| a[:query] == params &&
179
+ a[:payload].nil? }).and_return(nil)
180
+ @client.send(:request, verb, @path, params).should be_nil
181
+ end
182
+ end
183
+ end
184
+
185
+ [:post, :put].each do |verb|
186
+ context "with #{verb.inspect}" do
187
+ it "uses JSON-encoded payload options for parameters" do
188
+ payload = {:pay => "load"}
189
+ json_payload = JSON.dump(payload)
190
+ @http_client.should_receive(verb).with("#{@url}#{@path}", on { |a| a[:payload] == json_payload &&
191
+ a[:query].nil? }).and_return(nil).once
192
+ @client.send(:request, verb, @path, payload).should be_nil
193
+ end
194
+ end
195
+ end
196
+
197
+ context "health check proc" do
198
+ it "removes user and password from URL when checking health" do
199
+ @url = "http://me:pass@my.com"
200
+ @client = RightScale::BalancedHttpClient.new(@url, :health_check_path => "/health-check")
201
+ @http_client.should_receive(:get).with("http://my.com/health-check", Hash).once
202
+ @client.instance_variable_get(:@health_check_proc).call(@url)
203
+ end
204
+
205
+ it "uses default path if none specified" do
206
+ @client = RightScale::BalancedHttpClient.new(@url)
207
+ @http_client.should_receive(:get).with("http://my.com/health-check", Hash).once
208
+ @client.instance_variable_get(:@health_check_proc).call(@url)
209
+ end
210
+
211
+ it "appends health check path to any existing path" do
212
+ @url = "http://my.com/foo"
213
+ @client = RightScale::BalancedHttpClient.new(@url)
214
+ @http_client.should_receive(:get).with("http://my.com/foo/health-check", Hash).once
215
+ @client.instance_variable_get(:@health_check_proc).call(@url)
216
+ end
217
+
218
+ it "uses fixed timeout values" do
219
+ @client = RightScale::BalancedHttpClient.new(@url, :open_timeout => 5, :request_timeout => 30)
220
+ @http_client.should_receive(:get).with(String, {:open_timeout => 2, :timeout => 5}).once
221
+ @client.instance_variable_get(:@health_check_proc).call(@url)
222
+ end
223
+
224
+ it "sets API version if specified" do
225
+ @client = RightScale::BalancedHttpClient.new(@url, :api_version => "2.0")
226
+ @http_client.should_receive(:get).with(String, hsh(:headers => {"X-API-Version" => "2.0"})).once
227
+ @client.instance_variable_get(:@health_check_proc).call(@url)
228
+ end
229
+ end
230
+
231
+ it "sends request using HTTPClient via balancer" do
232
+ @http_client.should_receive(:post).with("#{@url}#{@path}", Hash).and_return(nil).once
233
+ @balancer.should_receive(:request).and_yield(@url).once
234
+ @client.send(:request, :post, @path).should be_nil
235
+ end
236
+
237
+ it "returns location header for 201 response" do
238
+ @response.should_receive(:code).and_return(201).once
239
+ @response.should_receive(:body).and_return("").once
240
+ @response.should_receive(:headers).and_return({:status => "201 Created", :location => "/href"}).once
241
+ @balancer.should_receive(:request).and_return(@response).once
242
+ @client.send(:request, :get, @path).should == "/href"
243
+ end
244
+
245
+ it "returns JSON decoded response" do
246
+ @response.should_receive(:body).and_return(@json_result).once
247
+ @balancer.should_receive(:request).and_return(@response).once
248
+ @client.send(:request, :get, @path).should == @json_decoded_result
249
+ end
250
+
251
+ it "returns nil if response is empty" do
252
+ @response.should_receive(:body).and_return("").once
253
+ @balancer.should_receive(:request).and_return(@response).once
254
+ @client.send(:request, :get, @path).should be_nil
255
+ end
256
+
257
+ it "returns nil if response is nil" do
258
+ @balancer.should_receive(:request).and_return(nil).once
259
+ @client.send(:request, :get, @path).should be_nil
260
+ end
261
+
262
+ it "returns nil if response status indicates no content" do
263
+ @response.should_receive(:code).and_return(204).once
264
+ @balancer.should_receive(:request).and_return(@response).once
265
+ @client.send(:request, :get, @path).should be_nil
266
+ end
267
+
268
+ it "handles NoResult response from balancer" do
269
+ no_result = RightSupport::Net::NoResult.new("no result", {})
270
+ @log.should_receive(:error).with(/Failed <random uuid>.*#{@path}/).once
271
+ @balancer.should_receive(:request).and_yield(@url).once
272
+ flexmock(RightSupport::Net::HTTPClient).should_receive(:new).and_raise(no_result).once
273
+ flexmock(@client).should_receive(:handle_no_result).with(RightSupport::Net::NoResult, @url, Proc).and_yield(no_result).once
274
+ @client.send(:request, :get, @path)
275
+ end
276
+
277
+ it "reports and re-raises unexpected exception" do
278
+ @log.should_receive(:error).with(/Failed <random uuid>.*#{@path}/).once
279
+ @balancer.should_receive(:request).and_raise(RuntimeError).once
280
+ lambda { @client.send(:request, :get, @path) }.should raise_error(RuntimeError)
281
+ end
282
+
283
+ context "when logging" do
284
+ before(:each) do
285
+ now = Time.now
286
+ flexmock(Time).should_receive(:now).and_return(now, now + 0.01)
287
+ end
288
+
289
+ it "logs request and response" do
290
+ @http_client.should_receive(:post).and_return(@response)
291
+ @log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar").once
292
+ @log.should_receive(:info).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes").once
293
+ @client.send(:request, :post, @path)
294
+ end
295
+
296
+ it "logs using specified log level" do
297
+ @http_client.should_receive(:post).and_return(@response)
298
+ @log.should_receive(:debug).with("Requesting POST <random uuid> /foo/bar").once
299
+ @log.should_receive(:debug).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes").once
300
+ @client.send(:request, :post, @path, {}, :log_level => :debug)
301
+ end
302
+
303
+ it "logs response length using header :content_length if available" do
304
+ @response.should_receive(:headers).and_return({:status => "200 OK", :content_length => 99})
305
+ @http_client.should_receive(:post).and_return(@response)
306
+ @log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar").once
307
+ @log.should_receive(:info).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 99 bytes").once
308
+ @client.send(:request, :post, @path)
309
+ end
310
+
311
+ it "omits user and password from logged host" do
312
+ url = "http://111:secret@my.com"
313
+ @client = RightScale::BalancedHttpClient.new([url])
314
+ @log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar").once
315
+ @log.should_receive(:info).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes").once
316
+ @http_client.should_receive(:post).with("#{url}#{@path}", Hash).and_return(@response).once
317
+ @balancer.should_receive(:request).and_yield(url).once
318
+ @client.send(:request, :post, @path)
319
+ end
320
+
321
+ context "when filtering" do
322
+ before(:each) do
323
+ @client = RightScale::BalancedHttpClient.new(@urls, :filter_params => [:secret])
324
+ @params = {:public => "data", "secret" => "data", "very secret" => "data"}
325
+ @options = {:filter_params => ["very secret"]}
326
+ @filtered_params = "{:public=>\"data\", \"secret\"=>\"<hidden>\", \"very secret\"=>\"<hidden>\"}"
327
+ end
328
+
329
+ it "logs request with filtered params if in debug mode" do
330
+ @log.should_receive(:level).and_return(:debug)
331
+ @http_client.should_receive(:post).and_return(@response)
332
+ @log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar #{@filtered_params}").once
333
+ @log.should_receive(:info).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes | {\"out\"=>123}").once
334
+ @client.send(:request, :post, @path, @params, @options)
335
+ end
336
+
337
+ it "logs response failure including filtered params" do
338
+ @http_client.should_receive(:post).and_raise(RestExceptionMock.new(400, "bad data")).once
339
+ @log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar").once
340
+ @log.should_receive(:error).with("Failed <random uuid> in 10ms | 400 [http://my.com/foo/bar #{@filtered_params}] | 400 Bad Request: bad data").once
341
+ lambda { @client.send(:request, :post, @path, @params, @options) }.should raise_error(RuntimeError)
342
+ end
343
+ end
344
+ end
345
+ end
346
+
347
+ context :handle_no_result do
348
+ before(:each) do
349
+ @no_result = RightSupport::Net::NoResult.new("no result", {})
350
+ @client = RightScale::BalancedHttpClient.new(@urls)
351
+ @yielded = nil
352
+ @proc = lambda { |e| @yielded = e }
353
+ end
354
+
355
+ it "uses configured server name" do
356
+ @client = RightScale::BalancedHttpClient.new(@urls, :server_name => "Some server")
357
+ lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
358
+ raise_error(RightScale::BalancedHttpClient::NotResponding, "Some server not responding")
359
+ end
360
+
361
+ it "defaults server name to host name" do
362
+ lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
363
+ raise_error(RightScale::BalancedHttpClient::NotResponding, "http://my.com not responding")
364
+ end
365
+
366
+ it "uses last exception stored in NoResult details" do
367
+ gateway_timeout = RestExceptionMock.new(504, "server timeout")
368
+ bad_request = RestExceptionMock.new(400, "bad data")
369
+ @no_result = RightSupport::Net::NoResult.new("no result", {@url => gateway_timeout, @url => bad_request})
370
+ lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should raise_error(bad_request)
371
+ @yielded.should == bad_request
372
+ end
373
+
374
+ it "uses server name in NotResponding exception if there are no details" do
375
+ lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
376
+ raise_error(RightScale::BalancedHttpClient::NotResponding, "http://my.com not responding")
377
+ @yielded.should == @no_result
378
+ end
379
+
380
+ it "uses http_body in raised NotResponding exception if status code is 504" do
381
+ gateway_timeout = RestExceptionMock.new(504, "server timeout")
382
+ @no_result = RightSupport::Net::NoResult.new("no result", {@url => gateway_timeout})
383
+ lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
384
+ raise_error(RightScale::BalancedHttpClient::NotResponding, "server timeout")
385
+ @yielded.should == gateway_timeout
386
+ end
387
+
388
+ it "uses raise NotResponding if status code is 504 and http_body is nil or empty" do
389
+ gateway_timeout = RestExceptionMock.new(504, "")
390
+ @no_result = RightSupport::Net::NoResult.new("no result", {@url => gateway_timeout})
391
+ lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
392
+ raise_error(RightScale::BalancedHttpClient::NotResponding, "http://my.com not responding")
393
+ @yielded.should == gateway_timeout
394
+ end
395
+
396
+ [502, 503].each do |code|
397
+ it "uses server name in NotResponding exception if status code is #{code}" do
398
+ e = RestExceptionMock.new(code)
399
+ @no_result = RightSupport::Net::NoResult.new("no result", {@url => e})
400
+ lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should \
401
+ raise_error(RightScale::BalancedHttpClient::NotResponding, "http://my.com not responding")
402
+ end
403
+ end
404
+
405
+ it "raises last exception in details if not retryable" do
406
+ bad_request = RestExceptionMock.new(400, "bad data")
407
+ @no_result = RightSupport::Net::NoResult.new("no result", {@url => bad_request})
408
+ lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should raise_error(bad_request)
409
+ @yielded.should == bad_request
410
+ end
411
+ end
412
+
413
+ context :report_failure do
414
+ before(:each) do
415
+ @started_at = Time.now
416
+ flexmock(Time).should_receive(:now).and_return(@started_at + 0.01)
417
+ @client = RightScale::BalancedHttpClient.new(@urls)
418
+ end
419
+
420
+ it "logs exception" do
421
+ exception = RestExceptionMock.new(400, "bad data")
422
+ @log.should_receive(:error).with("Failed <uuid> in 10ms | 400 [http://my.com/foo/bar \"params\"] | 400 Bad Request: bad data").once
423
+ @client.send(:report_failure, @url, @path, "params", [], "uuid", @started_at, exception)
424
+ end
425
+
426
+ it "logs error string" do
427
+ @log.should_receive(:error).with("Failed <uuid> in 10ms | nil [http://my.com/foo/bar \"params\"] | bad data").once
428
+ @client.send(:report_failure, @url, @path, "params", [], "uuid", @started_at, "bad data")
429
+ end
430
+
431
+ it "filters params" do
432
+ @log.should_receive(:error).with("Failed <uuid> in 10ms | nil [http://my.com/foo/bar {:secret=>\"<hidden>\"}] | bad data").once
433
+ @client.send(:report_failure, @url, @path, {:secret => "data"}, ["secret"], "uuid", @started_at, "bad data")
434
+ end
435
+ end
436
+
437
+ context :log_text do
438
+ before(:each) do
439
+ @client = RightScale::BalancedHttpClient.new(@urls)
440
+ end
441
+
442
+ context "when no exception" do
443
+
444
+ context "in info mode with no host" do
445
+ it "generates text containing path" do
446
+ text = @client.send(:log_text, @path, {:value => 123}, [])
447
+ text.should == "/foo/bar"
448
+ end
449
+ end
450
+
451
+ context "in info mode with host" do
452
+ it "generates text containing host and path" do
453
+ text = @client.send(:log_text, @path, {:value => 123}, [], @url)
454
+ text.should == "[http://my.com/foo/bar]"
455
+ end
456
+ end
457
+
458
+ context "and in debug mode" do
459
+ it "generates text containing containing host, path, and filtered params" do
460
+ @log.should_receive(:level).and_return(:debug)
461
+ text = @client.send(:log_text, @path, {:some => "data", :secret => "data"}, ["secret"], @url)
462
+ text.should == "[http://my.com/foo/bar {:some=>\"data\", :secret=>\"<hidden>\"}]"
463
+ end
464
+ end
465
+ end
466
+
467
+ context "when exception" do
468
+ it "includes params regardless of mode" do
469
+ text = @client.send(:log_text, @path, {:some => "data", :secret => "data"}, ["secret"], @url, "failed")
470
+ text.should == "[http://my.com/foo/bar {:some=>\"data\", :secret=>\"<hidden>\"}] | failed"
471
+ end
472
+
473
+ it "includes exception text" do
474
+ exception = RestExceptionMock.new(400, "bad data")
475
+ text = @client.send(:log_text, @path, {:some => "data", :secret => "data"}, ["secret"], @url, exception)
476
+ text.should == "[http://my.com/foo/bar {:some=>\"data\", :secret=>\"<hidden>\"}] | 400 Bad Request: bad data"
477
+ end
478
+ end
479
+ end
480
+
481
+ context :filter do
482
+ before(:each) do
483
+ @client = RightScale::BalancedHttpClient.new(@urls)
484
+ end
485
+
486
+ it "applies filters" do
487
+ filter = ["secret"]
488
+ params = {:some => 1, "secret" => "data"}
489
+ @client.send(:filter, params, filter).should == {:some => 1, "secret" => "<hidden>"}
490
+ end
491
+
492
+ it "converts param names to string before comparing to filter list" do
493
+ filter = ["secret", "very secret"]
494
+ params = {:some => 1, :secret => "data", "very secret" => "data"}
495
+ @client.send(:filter, params, filter).should == {:some => 1, :secret => "<hidden>", "very secret" => "<hidden>"}
496
+ end
497
+
498
+ it "does not filter if no filters are specified" do
499
+ params = {:some => 1, "secret" => "data"}
500
+ @client.send(:filter, params, []).should == params
501
+ end
502
+
503
+ it "does not filter if params is not a hash" do
504
+ @client.send(:filter, "params", ["secret"]).should == "params"
505
+ end
506
+ end
507
+
508
+ context :split do
509
+ before(:each) do
510
+ @client = RightScale::BalancedHttpClient.new(@urls)
511
+ end
512
+
513
+ it "leaves array as an array" do
514
+ @client.send(:split, ["data"]).should == ["data"]
515
+ end
516
+
517
+ it "turns nil into an empty array" do
518
+ @client.send(:split, nil).should == []
519
+ end
520
+
521
+ it "splits a string using default pattern" do
522
+ @client.send(:split, "some,data").should == ["some", "data"]
523
+ end
524
+
525
+ it "splits a string using specified pattern" do
526
+ @client.send(:split, "some#data", /#/).should == ["some", "data"]
527
+ end
528
+ end
529
+
530
+ context :exception_text do
531
+ context "when string exception" do
532
+ it "adds exception text" do
533
+ RightScale::BalancedHttpClient.exception_text("failed").should == "failed"
534
+ end
535
+ end
536
+
537
+ context "when REST exception" do
538
+ it "adds exception code/type and any http_body" do
539
+ exception = RestExceptionMock.new(400, "bad data")
540
+ RightScale::BalancedHttpClient.exception_text(exception).should == "400 Bad Request: bad data"
541
+ end
542
+
543
+ it "adds exception code/type but omits http_body if it is html" do
544
+ exception = RestExceptionMock.new(400, "<html> bad </html>")
545
+ RightScale::BalancedHttpClient.exception_text(exception).should == "400 Bad Request"
546
+ end
547
+
548
+ it "adds exception code/type and omits http_body if it is blank" do
549
+ exception = RestExceptionMock.new(400)
550
+ RightScale::BalancedHttpClient.exception_text(exception).should == "400 Bad Request"
551
+ end
552
+ end
553
+
554
+ context "when NoResult exception" do
555
+ it "adds exception class and message" do
556
+ exception = RightSupport::Net::NoResult.new("no result")
557
+ RightScale::BalancedHttpClient.exception_text(exception).should == "RightSupport::Net::NoResult: no result"
558
+ end
559
+ end
560
+
561
+ context "when non-REST, non-NoResult exception" do
562
+ it "adds exception class and message" do
563
+ exception = ArgumentError.new("bad arg")
564
+ RightScale::BalancedHttpClient.exception_text(exception).should == "ArgumentError: bad arg"
565
+ end
566
+ end
567
+
568
+ context "when non-REST, non-NoResult exception with backtrace" do
569
+ it "adds exception class, message, and backtrace" do
570
+ exception = ArgumentError.new("bad arg")
571
+ flexmock(exception).should_receive(:backtrace).and_return(["line 1", "line 2"])
572
+ RightScale::BalancedHttpClient.exception_text(exception).should == "ArgumentError: bad arg in\nline 1\nline 2"
573
+ end
574
+ end
575
+ end
576
+ end