This repository has been archived by the owner on Nov 17, 2017. It is now read-only.
/
sinagios_client_spec.rb
392 lines (321 loc) · 13.4 KB
/
sinagios_client_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
require 'sinagios_client'
require 'rspec'
require 'spec_helper'
require 'stringio'
describe SinagiosClient do
# We capture stderr output to an object and read from it in the tests
before(:each) do
$stderr = StringIO.new()
end
after(:each) do
$stderr = STDERR
end
describe '#new' do
it 'prints usage information when there are no command line arguments given' do
expect { SinagiosClient.new([]) }.to raise_error(SystemExit)
$stderr.string.should =~ /Usage:/
end
it 'prints usage information when insufficient command line arguments are given' do
expect { SinagiosClient.new(['--uri', 'http://api.example.com/']) }.to raise_error(SystemExit)
$stderr.string.should =~ /Usage:/
end
it 'gracefully handles incorrect command line options' do
expect { SinagiosClient.new(['--url', 'http://api.example.com/']) }.to raise_error(SystemExit)
$stderr.string.should =~ /invalid option: --url/
end
it 'prints nothing when sufficient command line arguments are given' do
expect { SinagiosClient.new(['--uri', 'http://api.example.com/', '--hosts', 'host1', '--operation', 'delete']) }.to_not raise_error(SystemExit)
$stderr.string.should == ""
end
it 'catches poorly formed URIs' do
expect { SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', 'foo bar baz']) }.to raise_error(SystemExit)
$stderr.string.should =~ /bad URI/
end
end
describe '#check_valid_options' do
it 'prints the list of required options when insufficient options are given' do
sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', 'http://api.example.com/'])
expect { sc.send(:check_valid_options, [:foo, :bar]) }.to raise_error(SystemExit)
$stderr.string.should =~ /Minimum required arguments.*--foo.*--bar/
end
end
describe '#parse_uri' do
it 'catches poorly formed URIs' do
expect { SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', 'foo bar baz']) }.to raise_error(SystemExit)
$stderr.string.should =~ /bad URI/
end
it 'accepts correctly formed URIs' do
sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', 'http://api.example.com/'])
sc.instance_eval do
@uri.class.should == URI::HTTP
end
end
end
describe '#run' do
it 'prints contextual usage information when an invalid operation is given' do
expect do
sc = SinagiosClient.new(['--operation', 'foobar'])
sc.run()
end.to raise_error(SystemExit)
$stderr.string.should =~ /Operation \'foobar\' is not supported/
end
end
describe '#parse_config_file' do
it 'sets options based on the config file if it exists' do
fakeconfig = File.join(File.dirname(__FILE__), 'test_data/fakeconfig.conf')
SinagiosClient.any_instance.expects(:config_file_path).twice.returns(fakeconfig)
expect do
sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1'])
sc.instance_eval do
@uri.to_s == 'http://example.com/api/'
@options[:author].should == 'testdude'
@options[:password].should == 'p455w0rd'
@options[:warnings].should == false
end
end.to_not raise_error
end
end
describe '#schedule' do
pending
end
describe '#delete' do
before(:each) do
# mock out the starting checks and connection setup as we need to intercept the request
@sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', 'http://api.example.com/'])
@sc.expects(:check_valid_options)
@sc.expects(:set_up_connection)
# save outputs for checking
$stdout, $stderr = StringIO.new(), StringIO.new()
end
after(:each) do
# Reset outputs to normal
$stdout, $stderr = STDOUT, STDERR
end
it 'returns successfully when downtime has been deleted' do
# mock HTTP request with a 200 result
http = mock('Net::HTTP')
result = mock('Net::HTTP result')
result.expects(:code).at_least_once.returns('200')
http.expects(:request).returns(result)
http.expects(:started?).returns(true)
http.expects(:finish)
@sc.instance_eval do
@http = http
end
# Call the delete and check for correct output
@sc.send(:delete)
$stdout.string.should =~ /Downtime deleted for host1/
end
it 'makes a second request with authentication when a 401 is received' do
# mock HTTP requests
http = mock('Net::HTTP')
# http request results with 401 and 200 codes
result401 = mock('Net::HTTP result 401')
result401.expects(:code).at_least_once.returns('401')
result200 = mock('Net::HTTP result 200')
result200.expects(:code).at_least_once.returns('200')
# Subsequent requests occur in order
http.expects(:request).at_least_once.returns(result401, result200)
http.expects(:started?).returns(true)
http.expects(:finish)
# Set up the mock http object and set auth details
@sc.instance_eval do
@http = http
@options[:author] = 'testdude'
@options[:password] = 'testpass'
end
# Call the delete and check for correct output
@sc.send(:delete)
$stdout.string.should =~ /Downtime deleted for host1/
end
it 'fails when an authenticated request is sent to the server but fails' do
# mock HTTP requests
http = mock('Net::HTTP')
# http request results with just authentication failed code
result401 = mock('Net::HTTP result 401')
result401.expects(:code).at_least_once.returns('401')
# Subsequent requests occur in order
http.expects(:request).at_least_once.returns(result401)
http.expects(:started?).returns(true)
http.expects(:finish)
# Set up the mock http object and set auth details
@sc.instance_eval do
@http = http
@options[:author] = 'testdude'
@options[:password] = 'testpass'
end
# Call the delete and check for error output
@sc.send(:delete)
$stderr.string.should =~ /401 - authentication failed/
end
it 'fails when downtime was not found by the server' do
# mock HTTP requests
http = mock('Net::HTTP')
# http request results with not found code
result404 = mock('Net::HTTP result 404')
result404.expects(:code).at_least_once.returns('404')
# Subsequent requests occur in order
http.expects(:request).at_least_once.returns(result404)
http.expects(:started?).returns(true)
http.expects(:finish)
# Set up the mock http object
@sc.instance_eval do
@http = http
end
# Call the delete and check for error output
@sc.send(:delete)
$stderr.string.should =~ /404 - downtime for host1 was not found/
end
it 'makes a request to the server for each host in a list of hosts' do
# mock HTTP requests
http = mock('Net::HTTP')
# http request results with success. We expect 3 calls per host (9 total).
result200 = mock('Net::HTTP result 200')
result200.expects(:code).times(9).returns('200')
# Subsequent requests occur in order
http.expects(:request).at_least_once.returns(result200)
http.expects(:started?).returns(true)
http.expects(:finish)
# Set up the mock http object, and add three hosts to be processed
@sc.instance_eval do
@http = http
@options[:hosts] = ['host1', 'host2', 'host3']
end
# Call the delete and check for error output
@sc.send(:delete)
$stdout.string.should == "Downtime deleted for host1\nDowntime deleted for host2\nDowntime deleted for host3\n"
end
end
describe '#check_auth_creds' do
it 'exits with an error when insufficient credentials are given' do
sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', 'http://api.example.com/'])
sc.expects(:finish_connection)
expect do
sc.send(:check_auth_creds)
end.to raise_error(SystemExit)
$stderr.string.should =~ /authentication was requested/
end
it 'passes when sufficient credentials are given' do
sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', 'http://api.example.com/', '--author', 'testdude', '--password', 'testpass'])
sc.send(:check_auth_creds)
end
end
describe '#set_request_auth' do
it 'sets the basic authentication header if authentication is required' do
# Create the object and enable authentication by force
sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', 'http://api.example.com/', '--author', 'testdude', '--password', 'testpass'])
sc.instance_eval do
@options[:use_auth] = true
end
# Set up a somewhat realistic request to test with auth
req = Net::HTTP::Get.new('foo')
sc.send(:set_request_auth, req)
# Verify the request has the authentication header
req['authorization'].should == 'Basic dGVzdGR1ZGU6dGVzdHBhc3M='
end
it 'does nothing if authentication is not required' do
# Create the object
sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', 'http://api.example.com/', '--author', 'testdude', '--password', 'testpass'])
# Set up a somewhat realistic request to test
req = Net::HTTP::Get.new('foo')
sc.send(:set_request_auth, req)
# Verify the request has no authentication header
req.key?('authorization').should == false
end
end
describe '#post_request' do
it 'correctly generates a post request' do
sc = SinagiosClient.new(['--operation', 'schedule', '--hosts', 'host1', '--uri', 'http://api.example.com/', '--author', 'testdude', '--comment', 'comment', '--duration', '300'])
# Mock out the various objects
req = mock('Net::HTTP::Post')
http = mock('Net::HTTP')
formdata = mock()
req.expects(:set_form_data).with(formdata)
http.expects(:request).with(req).returns('success')
sc.instance_eval do
@http = http
end
Net::HTTP::Post.expects(:new).with('/downtime/host1').returns(req)
sc.send(:post_request, 'host1', formdata).should == 'success'
end
end
describe '#delete_request' do
it 'correctly generates a delete request' do
sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', 'http://api.example.com/'])
# Mock out the various objects
req = mock('Net::HTTP::Delete')
http = mock('Net::HTTP')
http.expects(:request).with(req).returns('success')
sc.instance_eval do
@http = http
end
Net::HTTP::Delete.expects(:new).with('/downtime/host1').returns(req)
sc.send(:delete_request, 'host1').should == 'success'
end
end
describe '#request_path' do
it 'assembles the correct request path for a given hostname' do
sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', 'http://api.example.com/'])
sc.send(:request_path, 'host1').should == '/downtime/host1'
end
end
describe '#set_up_connection' do
it 'sets the transport mode to SSL when the URI scheme is https' do
url = 'https://api.example.com/'
sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', url])
# stub out the Net::HTTP library with pre-canned URI/connection details
uri = URI.parse(url)
http = mock('Net::HTTP')
http.expects(:use_ssl=).with(true)
Net::HTTP.expects(:new).with(uri.host, uri.port).returns(http)
sc.send(:set_up_connection)
end
it 'disables SSL warnings when set in the options' do
url = 'https://api.example.com/'
sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', url, '--no-warnings'])
# stub out the Net::HTTP and OpenSSL library calls.
uri = URI.parse(url)
http = mock('Net::HTTP')
http.expects(:use_ssl=).with(true)
openssl = mock('OpenSSL::SSL:SSLContext')
openssl.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
OpenSSL::SSL::SSLContext.expects(:new).returns(openssl)
Net::HTTP.expects(:new).with(uri.host, uri.port).returns(http)
sc.send(:set_up_connection)
end
it 'leaves the transport mode as plaintext when the URI scheme is http' do
url = 'http://api.example.com/'
sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', url])
# stub out the Net::HTTP library with pre-canned URI/connection details
uri = URI.parse(url)
http = mock('Net::HTTP')
Net::HTTP.expects(:new).with(uri.host, uri.port).returns(http)
sc.send(:set_up_connection)
end
end
describe '#finish_connection' do
it 'closes the http connection if it is open' do
sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', 'http://api.example.com/'])
# mock out the connection
http = mock('Net::HTTP')
http.expects(:started?).returns(true)
http.expects(:finish)
# inject the mock
sc.instance_eval do
@http = http
end
sc.send(:finish_connection)
end
it 'does nothing if the http connection is not open' do
sc = SinagiosClient.new(['--operation', 'delete', '--hosts', 'host1', '--uri', 'http://api.example.com/'])
# mock out the connection
http = mock('Net::HTTP')
http.expects(:started?).returns(false)
# inject the mock
sc.instance_eval do
@http = http
end
sc.send(:finish_connection)
end
end
end