right_agent 2.0.7-x86-mingw32

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 (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