exception_handling 2.9.0 → 3.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +0 -3
  3. data/.ruby-version +1 -1
  4. data/Gemfile +16 -11
  5. data/Gemfile.lock +104 -96
  6. data/README.md +3 -7
  7. data/Rakefile +11 -8
  8. data/exception_handling.gemspec +10 -10
  9. data/lib/exception_handling/exception_info.rb +10 -13
  10. data/lib/exception_handling/honeybadger_callbacks.rb +1 -1
  11. data/lib/exception_handling/log_stub_error.rb +1 -2
  12. data/lib/exception_handling/methods.rb +53 -6
  13. data/lib/exception_handling/testing.rb +10 -20
  14. data/lib/exception_handling/version.rb +1 -1
  15. data/lib/exception_handling.rb +13 -42
  16. data/semaphore_ci/setup.sh +3 -0
  17. data/{spec → test}/helpers/controller_helpers.rb +0 -0
  18. data/{spec → test}/helpers/exception_helpers.rb +2 -2
  19. data/{spec/spec_helper.rb → test/test_helper.rb} +42 -63
  20. data/test/unit/exception_handling/exception_catalog_test.rb +85 -0
  21. data/test/unit/exception_handling/exception_description_test.rb +82 -0
  22. data/{spec/unit/exception_handling/exception_info_spec.rb → test/unit/exception_handling/exception_info_test.rb} +114 -153
  23. data/{spec/unit/exception_handling/honeybadger_callbacks_spec.rb → test/unit/exception_handling/honeybadger_callbacks_test.rb} +20 -20
  24. data/{spec/unit/exception_handling/log_error_stub_spec.rb → test/unit/exception_handling/log_error_stub_test.rb} +22 -38
  25. data/{spec/unit/exception_handling/mailer_spec.rb → test/unit/exception_handling/mailer_test.rb} +18 -17
  26. data/test/unit/exception_handling/methods_test.rb +84 -0
  27. data/test/unit/exception_handling/sensu_test.rb +52 -0
  28. data/test/unit/exception_handling_test.rb +1109 -0
  29. metadata +53 -86
  30. data/.jenkins/Jenkinsfile +0 -113
  31. data/.jenkins/ruby_build_pod.yml +0 -18
  32. data/.rspec +0 -3
  33. data/Appraisals +0 -19
  34. data/CHANGELOG.md +0 -77
  35. data/gemfiles/.bundle/config +0 -2
  36. data/gemfiles/rails_4.gemfile +0 -19
  37. data/gemfiles/rails_5.gemfile +0 -19
  38. data/gemfiles/rails_6.gemfile +0 -19
  39. data/lib/exception_handling/escalate_callback.rb +0 -19
  40. data/lib/exception_handling/logging_methods.rb +0 -27
  41. data/spec/rake_test_warning_false.rb +0 -20
  42. data/spec/unit/exception_handling/escalate_callback_spec.rb +0 -81
  43. data/spec/unit/exception_handling/exception_catalog_spec.rb +0 -85
  44. data/spec/unit/exception_handling/exception_description_spec.rb +0 -82
  45. data/spec/unit/exception_handling/logging_methods_spec.rb +0 -38
  46. data/spec/unit/exception_handling/methods_spec.rb +0 -105
  47. data/spec/unit/exception_handling/sensu_spec.rb +0 -51
  48. data/spec/unit/exception_handling_spec.rb +0 -1249
@@ -1,1249 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require File.expand_path('../spec_helper', __dir__)
4
- require_test_helper 'controller_helpers'
5
- require_test_helper 'exception_helpers'
6
-
7
- describe ExceptionHandling do
8
- include ControllerHelpers
9
- include ExceptionHelpers
10
-
11
- before do
12
- @fail_count = 0
13
- end
14
-
15
- def dont_stub_log_error
16
- true
17
- end
18
-
19
- def append_organization_info_config(data)
20
- data[:user_details] = {}
21
- data[:user_details][:username] = "CaryP"
22
- data[:user_details][:organization] = "Invoca Engineering Dept."
23
- rescue StandardError
24
- # don't let these out!
25
- end
26
-
27
- def custom_data_callback_returns_nil_message_exception(_data)
28
- raise_exception_with_nil_message
29
- end
30
-
31
- def log_error_callback(_data, _ex, _treat_like_warning, _honeybadger_status)
32
- @fail_count += 1
33
- end
34
-
35
- def log_error_callback_config(data, _ex, treat_like_warning, honeybadger_status)
36
- @callback_data = data
37
- @treat_like_warning = treat_like_warning
38
- @fail_count += 1
39
- @honeybadger_status = honeybadger_status
40
- end
41
-
42
- def log_error_callback_with_failure(_data, _ex)
43
- raise "this should be rescued"
44
- end
45
-
46
- def log_error_callback_returns_nil_message_exception(_data, _ex)
47
- raise_exception_with_nil_message
48
- end
49
-
50
- module EventMachineStub
51
- class << self
52
- attr_accessor :block
53
-
54
- def schedule(&block)
55
- @block = block
56
- end
57
- end
58
- end
59
-
60
- class DNSResolvStub
61
- class << self
62
- attr_accessor :callback_block
63
- attr_accessor :errback_block
64
-
65
- def resolve(_hostname)
66
- self
67
- end
68
-
69
- def callback(&block)
70
- @callback_block = block
71
- end
72
-
73
- def errback(&block)
74
- @errback_block = block
75
- end
76
- end
77
- end
78
-
79
- class SmtpClientStub
80
- class << self
81
- attr_reader :block
82
- attr_reader :last_method
83
-
84
- def errback(&block)
85
- @block = block
86
- end
87
-
88
- def send_hash
89
- @send_hash ||= {}
90
- end
91
-
92
- def send(hash)
93
- @last_method = :send
94
- send_hash.clear
95
- send_hash.merge!(hash)
96
- self
97
- end
98
-
99
- def asend(hash)
100
- send(hash)
101
- @last_method = :asend
102
- self
103
- end
104
- end
105
- end
106
-
107
- class SmtpClientErrbackStub < SmtpClientStub
108
- end
109
-
110
- context "with warn and honeybadger notify stubbed" do
111
- before do
112
- allow(ExceptionHandling).to receive(:warn).with(anything)
113
- allow(Honeybadger).to receive(:notify).with(anything)
114
- end
115
-
116
- context "with logger stashed" do
117
- before { @original_logger = ExceptionHandling.logger }
118
- after { ExceptionHandling.logger = @original_logger }
119
-
120
- it "stores logger as-is if it has ContextualLogger::Mixin" do
121
- logger = Logger.new('/dev/null')
122
- logger.extend(ContextualLogger::LoggerMixin)
123
- ancestors = logger.singleton_class.ancestors.*.name
124
-
125
- ExceptionHandling.logger = logger
126
- expect(ExceptionHandling.logger.singleton_class.ancestors.*.name).to eq(ancestors)
127
- end
128
-
129
- it "allows logger = nil (no deprecation warning)" do
130
- expect(STDERR).to receive(:puts).with(/DEPRECATION WARNING/).never
131
- ExceptionHandling.logger = nil
132
- end
133
-
134
- it "[deprecated] mixes in ContextualLogger::Mixin if not there" do
135
- expect(STDERR).to receive(:puts).with(/DEPRECATION WARNING: implicit extend with ContextualLogger::LoggerMixin is deprecated and will be removed from exception_handling 3\.0/)
136
- logger = Logger.new('/dev/null')
137
- ancestors = logger.singleton_class.ancestors.*.name
138
-
139
- ExceptionHandling.logger = logger
140
- expect(ExceptionHandling.logger).to be_kind_of(ContextualLogger::LoggerMixin)
141
- expect(ExceptionHandling.logger.singleton_class.ancestors.*.name).to_not eq(ancestors)
142
- end
143
-
144
- context "#log_error" do
145
- it "takes in additional logging context hash and pass it to the logger" do
146
- ExceptionHandling.log_error('This is an Error', 'This is the prefix context', service_name: 'exception_handling')
147
- expect(logged_excluding_reload_filter.last[:message]).to match(/This is an Error/)
148
- expect(logged_excluding_reload_filter.last[:context]).to_not be_empty
149
- expect(service_name: 'exception_handling').to eq(logged_excluding_reload_filter.last[:context])
150
- end
151
-
152
- it "logs with Severity::FATAL" do
153
- ExceptionHandling.log_error('This is a Warning', service_name: 'exception_handling')
154
- expect('FATAL').to eq(logged_excluding_reload_filter.last[:severity])
155
- end
156
- end
157
- end
158
-
159
- context "#log_warning" do
160
- it "have empty array as a backtrace" do
161
- expect(ExceptionHandling).to receive(:log_error).with((ExceptionHandling::Warning), anything) do |error|
162
- expect(error.backtrace).to eq([])
163
- end
164
- ExceptionHandling.log_warning('Now with empty array as a backtrace!')
165
- end
166
-
167
- it "take in additional key word args as logging context and pass them to the logger" do
168
- ExceptionHandling.log_warning('This is a Warning', service_name: 'exception_handling')
169
- expect(logged_excluding_reload_filter.last[:message]).to match(/This is a Warning/)
170
- expect(logged_excluding_reload_filter.last[:context]).to_not be_empty
171
- expect(service_name: 'exception_handling').to eq(logged_excluding_reload_filter.last[:context])
172
- end
173
-
174
- it "log with Severity::WARN" do
175
- ExceptionHandling.log_warning('This is a Warning', service_name: 'exception_handling')
176
- expect('WARN').to eq(logged_excluding_reload_filter.last[:severity])
177
- end
178
- end
179
-
180
- context "#log_info" do
181
- it "take in additional key word args as logging context and pass them to the logger" do
182
- ExceptionHandling.log_info('This is an Info', service_name: 'exception_handling')
183
- expect(logged_excluding_reload_filter.last[:message]).to match(/This is an Info/)
184
- expect(logged_excluding_reload_filter.last[:context]).to_not be_empty
185
- expect(service_name: 'exception_handling').to eq(logged_excluding_reload_filter.last[:context])
186
- end
187
-
188
- it "log with Severity::INFO" do
189
- ExceptionHandling.log_info('This is a Warning', service_name: 'exception_handling')
190
- expect('INFO').to eq(logged_excluding_reload_filter.last[:severity])
191
- end
192
- end
193
-
194
- context "#log_debug" do
195
- it "take in additional key word args as logging context and pass them to the logger" do
196
- ExceptionHandling.log_debug('This is a Debug', service_name: 'exception_handling')
197
- expect(logged_excluding_reload_filter.last[:message]).to match(/This is a Debug/)
198
- expect(logged_excluding_reload_filter.last[:context]).to_not be_empty
199
- expect(service_name: 'exception_handling').to eq(logged_excluding_reload_filter.last[:context])
200
- end
201
-
202
- it "log with Severity::DEBUG" do
203
- ExceptionHandling.log_debug('This is a Warning', service_name: 'exception_handling')
204
- expect('DEBUG').to eq(logged_excluding_reload_filter.last[:severity])
205
- end
206
- end
207
-
208
- context "#write_exception_to_log" do
209
- it "log warnings with Severity::WARN" do
210
- warning = ExceptionHandling::Warning.new('This is a Warning')
211
- ExceptionHandling.write_exception_to_log(warning, '', Time.now.to_i, service_name: 'exception_handling')
212
- expect('WARN').to eq(logged_excluding_reload_filter.last[:severity])
213
- end
214
-
215
- it "log everything else with Severity::FATAL" do
216
- error = RuntimeError.new('This is a runtime error')
217
- ExceptionHandling.write_exception_to_log(error, '', Time.now.to_i, service_name: 'exception_handling')
218
- expect('FATAL').to eq(logged_excluding_reload_filter.last[:severity])
219
- end
220
- end
221
-
222
- context "configuration with custom_data_hook or post_log_error_hook" do
223
- after do
224
- ExceptionHandling.custom_data_hook = nil
225
- ExceptionHandling.post_log_error_hook = nil
226
- end
227
-
228
- it "support a custom_data_hook" do
229
- capture_notifications
230
-
231
- ExceptionHandling.custom_data_hook = method(:append_organization_info_config)
232
- ExceptionHandling.ensure_safe("context") { raise "Some Exception" }
233
-
234
- expect(sent_notifications.last.enhanced_data['user_details'].to_s).to match(/Invoca Engineering Dept./)
235
- end
236
-
237
- it "support a log_error hook, and pass exception_data, treat_like_warning, and logged_to_honeybadger to it" do
238
- @honeybadger_status = nil
239
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
240
-
241
- notify_args = []
242
- expect(Honeybadger).to receive(:notify).with(any_args) { |info| notify_args << info; '06220c5a-b471-41e5-baeb-de247da45a56' }
243
- ExceptionHandling.ensure_safe("context") { raise "Some Exception" }
244
- expect(@fail_count).to eq(1)
245
- expect(@treat_like_warning).to eq(false)
246
- expect(@honeybadger_status).to eq(:success)
247
-
248
- expect(@callback_data["notes"]).to eq("this is used by a test")
249
- expect(notify_args.size).to eq(1), notify_args.inspect
250
- expect(notify_args.last[:context].to_s).to match(/this is used by a test/)
251
- end
252
-
253
- it "plumb treat_like_warning and logged_to_honeybadger to log error hook" do
254
- @honeybadger_status = nil
255
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
256
- ExceptionHandling.log_error(StandardError.new("Some Exception"), "mooo", treat_like_warning: true)
257
- expect(@fail_count).to eq(1)
258
- expect(@treat_like_warning).to eq(true)
259
- expect(@honeybadger_status).to eq(:skipped)
260
- end
261
-
262
- it "include logging context in the exception data" do
263
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
264
- ExceptionHandling.log_error(StandardError.new("Some Exception"), "mooo", treat_like_warning: true, log_context_test: "contextual_logging")
265
-
266
- expected_log_context = {
267
- "log_context_test" => "contextual_logging"
268
- }
269
- expect(@callback_data[:log_context]).to eq(expected_log_context)
270
- end
271
-
272
- it "support rescue exceptions from a log_error hook" do
273
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_with_failure)
274
- log_info_messages = []
275
- allow(ExceptionHandling.logger).to receive(:info).with(any_args) do |message, _|
276
- log_info_messages << message
277
- end
278
- expect { ExceptionHandling.ensure_safe("mooo") { raise "Some Exception" } }.to_not raise_error
279
- expect(log_info_messages.find { |message| message =~ /Unable to execute custom log_error callback/ }).to be_truthy
280
- end
281
-
282
- it "handle nil message exceptions resulting from the log_error hook" do
283
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_returns_nil_message_exception)
284
- log_info_messages = []
285
- allow(ExceptionHandling.logger).to receive(:info).with(any_args) do |message, _|
286
- log_info_messages << message
287
- end
288
- expect { ExceptionHandling.ensure_safe("mooo") { raise "Some Exception" } }.to_not raise_error
289
- expect(log_info_messages.find { |message| message =~ /Unable to execute custom log_error callback/ }).to be_truthy
290
- end
291
-
292
- it "handle nil message exceptions resulting from the custom data hook" do
293
- ExceptionHandling.custom_data_hook = method(:custom_data_callback_returns_nil_message_exception)
294
- log_info_messages = []
295
- allow(ExceptionHandling.logger).to receive(:info).with(any_args) do |message, _|
296
- log_info_messages << message
297
- end
298
- expect { ExceptionHandling.ensure_safe("mooo") { raise "Some Exception" } }.not_to raise_error
299
- expect(log_info_messages.find { |message| message =~ /Unable to execute custom custom_data_hook callback/ }).to be_truthy
300
- end
301
- end
302
-
303
- context "Exception Handling" do
304
- context "default_metric_name" do
305
- context "when metric_name is present in exception_data" do
306
- it "include metric_name in resulting metric name" do
307
- exception = StandardError.new('this is an exception')
308
- metric = ExceptionHandling.default_metric_name({ 'metric_name' => 'special_metric' }, exception, true)
309
- expect(metric).to eq('exception_handling.special_metric')
310
- end
311
- end
312
-
313
- context "when metric_name is not present in exception_data" do
314
- it "return exception_handling.warning when using log warning" do
315
- warning = ExceptionHandling::Warning.new('this is a warning')
316
- metric = ExceptionHandling.default_metric_name({}, warning, false)
317
- expect(metric).to eq('exception_handling.warning')
318
- end
319
-
320
- it "return exception_handling.exception when using log error" do
321
- exception = StandardError.new('this is an exception')
322
- metric = ExceptionHandling.default_metric_name({}, exception, false)
323
- expect(metric).to eq('exception_handling.exception')
324
- end
325
-
326
- context "when using log error with treat_like_warning" do
327
- it "return exception_handling.unforwarded_exception when exception not present" do
328
- metric = ExceptionHandling.default_metric_name({}, nil, true)
329
- expect(metric).to eq('exception_handling.unforwarded_exception')
330
- end
331
-
332
- it "return exception_handling.unforwarded_exception with exception classname when exception is present" do
333
- module SomeModule
334
- class SomeException < StandardError
335
- end
336
- end
337
-
338
- exception = SomeModule::SomeException.new('this is an exception')
339
- metric = ExceptionHandling.default_metric_name({}, exception, true)
340
- expect(metric).to eq('exception_handling.unforwarded_exception_SomeException')
341
- end
342
- end
343
- end
344
- end
345
-
346
- context "default_honeybadger_metric_name" do
347
- it "return exception_handling.honeybadger.success when status is :success" do
348
- metric = ExceptionHandling.default_honeybadger_metric_name(:success)
349
- expect(metric).to eq('exception_handling.honeybadger.success')
350
- end
351
-
352
- it "return exception_handling.honeybadger.failure when status is :failure" do
353
- metric = ExceptionHandling.default_honeybadger_metric_name(:failure)
354
- expect(metric).to eq('exception_handling.honeybadger.failure')
355
- end
356
-
357
- it "return exception_handling.honeybadger.skipped when status is :skipped" do
358
- metric = ExceptionHandling.default_honeybadger_metric_name(:skipped)
359
- expect(metric).to eq('exception_handling.honeybadger.skipped')
360
- end
361
-
362
- it "return exception_handling.honeybadger.unknown_status when status is not recognized" do
363
- metric = ExceptionHandling.default_honeybadger_metric_name(nil)
364
- expect(metric).to eq('exception_handling.honeybadger.unknown_status')
365
- end
366
- end
367
-
368
- context "ExceptionHandling.ensure_safe" do
369
- it "log an exception with call stack if an exception is raised." do
370
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything)
371
- ExceptionHandling.ensure_safe { raise ArgumentError, "blah" }
372
- end
373
-
374
- if ActionView::VERSION::MAJOR >= 5
375
- it "log an exception with call stack if an ActionView template exception is raised." do
376
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(Error:\d+\) \nActionView::Template::Error: \(blah\):\n /, anything)
377
- ExceptionHandling.ensure_safe do
378
- begin
379
- # Rails 5 made the switch from ActionView::TemplateError taking in the original exception
380
- # as an argument to using the $! global to extract the original exception
381
- raise ArgumentError, "blah"
382
- rescue
383
- raise ActionView::TemplateError.new({})
384
- end
385
- end
386
- end
387
- else
388
- it "log an exception with call stack if an ActionView template exception is raised." do
389
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(Error:\d+\) \nActionView::Template::Error: \(blah\):\n /, anything)
390
- ExceptionHandling.ensure_safe { raise ActionView::TemplateError.new({}, ArgumentError.new("blah")) }
391
- end
392
- end
393
-
394
- it "should not log an exception if an exception is not raised." do
395
- expect(ExceptionHandling.logger).to_not receive(:fatal)
396
- ExceptionHandling.ensure_safe { ; }
397
- end
398
-
399
- it "return its value if used during an assignment" do
400
- expect(ExceptionHandling.logger).to_not receive(:fatal)
401
- b = ExceptionHandling.ensure_safe { 5 }
402
- expect(b).to eq(5)
403
- end
404
-
405
- it "return nil if an exception is raised during an assignment" do
406
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything)
407
- b = ExceptionHandling.ensure_safe { raise ArgumentError, "blah" }
408
- expect(b).to be_nil
409
- end
410
-
411
- it "allow a message to be appended to the error when logged." do
412
- expect(ExceptionHandling.logger).to receive(:fatal).with(/mooo\nArgumentError: \(blah\):\n.*exception_handling_spec\.rb/, anything)
413
- b = ExceptionHandling.ensure_safe("mooo") { raise ArgumentError, "blah" }
414
- expect(b).to be_nil
415
- end
416
-
417
- it "only rescue StandardError and descendents" do
418
- expect { ExceptionHandling.ensure_safe("mooo") { raise Exception } }.to raise_exception(Exception)
419
-
420
- expect(ExceptionHandling.logger).to receive(:fatal).with(/mooo\nStandardError: \(blah\):\n.*exception_handling_spec\.rb/, anything)
421
-
422
- b = ExceptionHandling.ensure_safe("mooo") { raise StandardError, "blah" }
423
- expect(b).to be_nil
424
- end
425
- end
426
-
427
- context "ExceptionHandling.ensure_completely_safe" do
428
- it "log an exception if an exception is raised." do
429
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything)
430
- ExceptionHandling.ensure_completely_safe { raise ArgumentError, "blah" }
431
- end
432
-
433
- it "should not log an exception if an exception is not raised." do
434
- expect(ExceptionHandling.logger).to receive(:fatal).exactly(0)
435
- ExceptionHandling.ensure_completely_safe { ; }
436
- end
437
-
438
- it "return its value if used during an assignment" do
439
- expect(ExceptionHandling.logger).to receive(:fatal).exactly(0)
440
- b = ExceptionHandling.ensure_completely_safe { 5 }
441
- expect(b).to eq(5)
442
- end
443
-
444
- it "return nil if an exception is raised during an assignment" do
445
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything) { nil }
446
- b = ExceptionHandling.ensure_completely_safe { raise ArgumentError, "blah" }
447
- expect(b).to be_nil
448
- end
449
-
450
- it "allow a message to be appended to the error when logged." do
451
- expect(ExceptionHandling.logger).to receive(:fatal).with(/mooo\nArgumentError: \(blah\):\n.*exception_handling_spec\.rb/, anything)
452
- b = ExceptionHandling.ensure_completely_safe("mooo") { raise ArgumentError, "blah" }
453
- expect(b).to be_nil
454
- end
455
-
456
- it "rescue any instance or child of Exception" do
457
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything)
458
- ExceptionHandling.ensure_completely_safe { raise Exception, "blah" }
459
- end
460
-
461
- it "not rescue the special exceptions that Ruby uses" do
462
- [SystemExit, SystemStackError, NoMemoryError, SecurityError].each do |exception|
463
- expect do
464
- ExceptionHandling.ensure_completely_safe do
465
- raise exception
466
- end
467
- end.to raise_exception(exception)
468
- end
469
- end
470
- end
471
-
472
- context "ExceptionHandling.ensure_escalation" do
473
- before do
474
- capture_notifications
475
- ActionMailer::Base.deliveries.clear
476
- end
477
-
478
- it "log the exception as usual and send the proper email" do
479
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything)
480
- ExceptionHandling.ensure_escalation("Favorite Feature") { raise ArgumentError, "blah" }
481
- expect(ActionMailer::Base.deliveries.count).to eq(1)
482
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
483
-
484
- email = ActionMailer::Base.deliveries.last
485
- expect(email.subject).to eq("#{ExceptionHandling.email_environment} Escalation: Favorite Feature")
486
- expect(email.body.to_s).to match('ArgumentError: blah')
487
- expect(email.body.to_s).to match(ExceptionHandling.last_exception_timestamp.to_s)
488
- end
489
-
490
- it "should not escalate if an exception is not raised." do
491
- expect(ExceptionHandling.logger).to_not receive(:fatal)
492
- ExceptionHandling.ensure_escalation('Ignored') { ; }
493
- expect(ActionMailer::Base.deliveries.count).to eq(0)
494
- end
495
-
496
- it "log if the escalation email cannot be sent" do
497
- expect_any_instance_of(Mail::Message).to receive(:deliver).and_raise(RuntimeError.new, "Delivery Error")
498
- log_fatals = []
499
- expect(ExceptionHandling.logger).to receive(:fatal).with(any_args).at_least(:once) do |*args|
500
- log_fatals << args
501
- end
502
-
503
- ExceptionHandling.ensure_escalation("ensure context") { raise ArgumentError, "first_test_exception" }
504
- expect(log_fatals[0].first).to match(/ArgumentError.*first_test_exception/)
505
- expect(log_fatals[1].first).to match(/safe_email_deliver.*Delivery Error/m)
506
-
507
- expect(log_fatals.size).to eq(2), log_fatals.inspect
508
-
509
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect # still sent to honeybadger
510
- end
511
-
512
- it "allow the caller to specify custom recipients" do
513
- custom_recipients = ['something@invoca.com']
514
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything)
515
- ExceptionHandling.ensure_escalation("Favorite Feature", custom_recipients) { raise ArgumentError, "blah" }
516
- expect(ActionMailer::Base.deliveries.count).to eq(1)
517
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
518
-
519
- email = ActionMailer::Base.deliveries.last
520
- expect(email.subject).to eq("#{ExceptionHandling.email_environment} Escalation: Favorite Feature")
521
- expect(email.body.to_s).to match('ArgumentError: blah')
522
- expect(email.body.to_s).to match(ExceptionHandling.last_exception_timestamp.to_s)
523
- expect(email.to).to eq(custom_recipients)
524
- end
525
- end
526
-
527
- context "ExceptionHandling.ensure_alert" do
528
- it "log the exception as usual and fire a sensu event" do
529
- expect(ExceptionHandling::Sensu).to receive(:generate_event).with("Favorite Feature", "test context\nblah")
530
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything)
531
- ExceptionHandling.ensure_alert('Favorite Feature', 'test context') { raise ArgumentError, "blah" }
532
- end
533
-
534
- it "should not send sensu event if an exception is not raised." do
535
- expect(ExceptionHandling.logger).to_not receive(:fatal)
536
- expect(ExceptionHandling::Sensu).to_not receive(:generate_event)
537
- ExceptionHandling.ensure_alert('Ignored', 'test context') { ; }
538
- end
539
-
540
- it "log if the sensu event could not be sent" do
541
- expect(ExceptionHandling::Sensu).to receive(:send_event).with(anything) { raise "Failed to send" }
542
- expect(ExceptionHandling.logger).to receive(:fatal).with(/first_test_exception/, anything)
543
- expect(ExceptionHandling.logger).to receive(:fatal).with(/Failed to send/, anything)
544
- ExceptionHandling.ensure_alert("Not Used", 'test context') { raise ArgumentError, "first_test_exception" }
545
- end
546
-
547
- it "log if the exception message is nil" do
548
- expect(ExceptionHandling::Sensu).to receive(:generate_event).with("some alert", "test context\n")
549
- ExceptionHandling.ensure_alert('some alert', 'test context') { raise_exception_with_nil_message }
550
- end
551
- end
552
-
553
- context "ExceptionHandling.escalate_to_production_support" do
554
- it "notify production support" do
555
- subject = "Runtime Error found!"
556
- exception = RuntimeError.new("Test")
557
- recipients = ["prodsupport@example.com"]
558
-
559
- expect(ExceptionHandling).to receive(:production_support_recipients).and_return(recipients).exactly(2)
560
- expect(ExceptionHandling).to receive(:escalate).with(subject, exception, ExceptionHandling.last_exception_timestamp, recipients)
561
- ExceptionHandling.escalate_to_production_support(exception, subject)
562
- end
563
- end
564
-
565
- context "exception timestamp" do
566
- before do
567
- Time.now_override = Time.parse('1986-5-21 4:17 am UTC')
568
- end
569
-
570
- it "include the timestamp when the exception is logged" do
571
- capture_notifications
572
-
573
- expect(ExceptionHandling.logger).to receive(:fatal).with(/\(Error:517033020\) context\nArgumentError: \(blah\):\n.*exception_handling_spec\.rb/, anything)
574
- b = ExceptionHandling.ensure_safe("context") { raise ArgumentError, "blah" }
575
- expect(b).to be_nil
576
-
577
- expect(ExceptionHandling.last_exception_timestamp).to eq(517_033_020)
578
-
579
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
580
-
581
- expect(sent_notifications.last.enhanced_data['timestamp']).to eq(517_033_020)
582
- end
583
- end
584
-
585
- it "log the error if the exception message is nil" do
586
- capture_notifications
587
-
588
- ExceptionHandling.log_error(exception_with_nil_message)
589
-
590
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
591
- expect(sent_notifications.last.enhanced_data['error_string']).to eq('RuntimeError: ')
592
- end
593
-
594
- it "log the error if the exception message is nil and the exception context is a hash" do
595
- capture_notifications
596
-
597
- ExceptionHandling.log_error(exception_with_nil_message, "SERVER_NAME" => "exceptional.com")
598
-
599
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
600
- expect(sent_notifications.last.enhanced_data['error_string']).to eq('RuntimeError: ')
601
- end
602
-
603
- context "Honeybadger integration" do
604
- context "with Honeybadger not defined" do
605
- before do
606
- allow(ExceptionHandling).to receive(:honeybadger_defined?) { false }
607
- end
608
-
609
- it "not invoke send_exception_to_honeybadger when log_error is executed" do
610
- expect(ExceptionHandling).to_not receive(:send_exception_to_honeybadger)
611
- ExceptionHandling.log_error(exception_1)
612
- end
613
-
614
- it "not invoke send_exception_to_honeybadger when ensure_safe is executed" do
615
- expect(ExceptionHandling).to_not receive(:send_exception_to_honeybadger)
616
- ExceptionHandling.ensure_safe { raise exception_1 }
617
- end
618
- end
619
-
620
- context "with Honeybadger defined" do
621
- it "not send_exception_to_honeybadger when log_warning is executed" do
622
- expect(ExceptionHandling).to_not receive(:send_exception_to_honeybadger)
623
- ExceptionHandling.log_warning("This should not go to honeybadger")
624
- end
625
-
626
- it "not send_exception_to_honeybadger when log_error is called with a Warning" do
627
- expect(ExceptionHandling).to_not receive(:send_exception_to_honeybadger)
628
- ExceptionHandling.log_error(ExceptionHandling::Warning.new("This should not go to honeybadger"))
629
- end
630
-
631
- it "invoke send_exception_to_honeybadger when log_error is executed" do
632
- expect(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(any_args).and_call_original
633
- ExceptionHandling.log_error(exception_1)
634
- end
635
-
636
- it "invoke send_exception_to_honeybadger when log_error_rack is executed" do
637
- expect(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(any_args).and_call_original
638
- ExceptionHandling.log_error_rack(exception_1, {}, nil)
639
- end
640
-
641
- it "invoke send_exception_to_honeybadger when ensure_safe is executed" do
642
- expect(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(any_args).and_call_original
643
- ExceptionHandling.ensure_safe { raise exception_1 }
644
- end
645
-
646
- it "specify error message as an empty string when notifying honeybadger if exception message is nil" do
647
- expect(Honeybadger).to receive(:notify).with(any_args) do |args|
648
- expect(args[:error_message]).to eq("")
649
- end
650
- ExceptionHandling.log_error(exception_with_nil_message)
651
- end
652
-
653
- context "with stubbed values" do
654
- before do
655
- Time.now_override = Time.now
656
- @env = { server: "fe98" }
657
- @parameters = { advertiser_id: 435, controller: "some_controller" }
658
- @session = { username: "jsmith" }
659
- @request_uri = "host/path"
660
- @controller = create_dummy_controller(@env, @parameters, @session, @request_uri)
661
- allow(ExceptionHandling).to receive(:server_name) { "invoca_fe98" }
662
-
663
- @exception = StandardError.new("Some Exception")
664
- @exception.set_backtrace([
665
- "spec/unit/exception_handling_spec.rb:847:in `exception_1'",
666
- "spec/unit/exception_handling_spec.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
667
- ])
668
- @exception_context = { "SERVER_NAME" => "exceptional.com" }
669
- end
670
-
671
- it "send error details and relevant context data to Honeybadger with log_context" do
672
- honeybadger_data = nil
673
- expect(Honeybadger).to receive(:notify).with(any_args) do |data|
674
- honeybadger_data = data
675
- end
676
- ExceptionHandling.logger.global_context = { service_name: "rails", region: "AWS-us-east-1" }
677
- log_context = { log_source: "gem/listen", service_name: "bin/console" }
678
- ExceptionHandling.log_error(@exception, @exception_context, @controller, **log_context) do |data|
679
- data[:scm_revision] = "5b24eac37aaa91f5784901e9aabcead36fd9df82"
680
- data[:user_details] = { username: "jsmith" }
681
- data[:event_response] = "Event successfully received"
682
- data[:other_section] = "This should not be included in the response"
683
- end
684
-
685
- expected_data = {
686
- error_class: :"Test Exception",
687
- error_message: "Some Exception",
688
- controller: "some_controller",
689
- exception: @exception,
690
- context: {
691
- timestamp: Time.now.to_i,
692
- error_class: "StandardError",
693
- server: "invoca_fe98",
694
- exception_context: { "SERVER_NAME" => "exceptional.com" },
695
- scm_revision: "5b24eac37aaa91f5784901e9aabcead36fd9df82",
696
- notes: "this is used by a test",
697
- user_details: { "username" => "jsmith" },
698
- request: {
699
- "params" => { "advertiser_id" => 435, "controller" => "some_controller" },
700
- "rails_root" => "Rails.root not defined. Is this a test environment?",
701
- "url" => "host/path"
702
- },
703
- session: {
704
- "key" => nil,
705
- "data" => { "username" => "jsmith" }
706
- },
707
- environment: {
708
- "SERVER_NAME" => "exceptional.com"
709
- },
710
- backtrace: [
711
- "spec/unit/exception_handling_spec.rb:847:in `exception_1'",
712
- "spec/unit/exception_handling_spec.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
713
- ],
714
- event_response: "Event successfully received",
715
- log_context: { "service_name" => "bin/console", "region" => "AWS-us-east-1", "log_source" => "gem/listen" }
716
- }
717
- }
718
- expect(honeybadger_data).to eq(expected_data)
719
- end
720
-
721
- it "send error details and relevant context data to Honeybadger with empty log_context" do
722
- honeybadger_data = nil
723
- expect(Honeybadger).to receive(:notify).with(any_args) do |data|
724
- honeybadger_data = data
725
- end
726
- ExceptionHandling.logger.global_context = {}
727
- log_context = {}
728
- ExceptionHandling.log_error(@exception, @exception_context, @controller, **log_context) do |data|
729
- data[:scm_revision] = "5b24eac37aaa91f5784901e9aabcead36fd9df82"
730
- data[:user_details] = { username: "jsmith" }
731
- data[:event_response] = "Event successfully received"
732
- data[:other_section] = "This should not be included in the response"
733
- end
734
-
735
- expected_data = {
736
- error_class: :"Test Exception",
737
- error_message: "Some Exception",
738
- controller: "some_controller",
739
- exception: @exception,
740
- context: {
741
- timestamp: Time.now.to_i,
742
- error_class: "StandardError",
743
- server: "invoca_fe98",
744
- exception_context: { "SERVER_NAME" => "exceptional.com" },
745
- scm_revision: "5b24eac37aaa91f5784901e9aabcead36fd9df82",
746
- notes: "this is used by a test",
747
- user_details: { "username" => "jsmith" },
748
- request: {
749
- "params" => { "advertiser_id" => 435, "controller" => "some_controller" },
750
- "rails_root" => "Rails.root not defined. Is this a test environment?",
751
- "url" => "host/path"
752
- },
753
- session: {
754
- "key" => nil,
755
- "data" => { "username" => "jsmith" }
756
- },
757
- environment: {
758
- "SERVER_NAME" => "exceptional.com"
759
- },
760
- backtrace: [
761
- "spec/unit/exception_handling_spec.rb:847:in `exception_1'",
762
- "spec/unit/exception_handling_spec.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
763
- ],
764
- event_response: "Event successfully received"
765
- }
766
- }
767
- expect(honeybadger_data).to eq(expected_data)
768
- end
769
- end
770
-
771
- context "with post_log_error_hook set" do
772
- after do
773
- ExceptionHandling.post_log_error_hook = nil
774
- end
775
-
776
- it "not send notification to honeybadger when exception description has the flag turned off and call log error callback with logged_to_honeybadger set to nil" do
777
- @honeybadger_status = nil
778
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
779
- filter_list = {
780
- NoHoneybadger: {
781
- error: "suppress Honeybadger notification",
782
- send_to_honeybadger: false
783
- }
784
- }
785
- allow(File).to receive(:mtime) { incrementing_mtime }
786
- expect(YAML).to receive(:load_file).with(any_args) { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }.at_least(1)
787
-
788
- expect(ExceptionHandling).to receive(:send_exception_to_honeybadger_unless_filtered).with(any_args).exactly(1).and_call_original
789
- expect(Honeybadger).to_not receive(:notify)
790
- ExceptionHandling.log_error(StandardError.new("suppress Honeybadger notification"))
791
- expect(@honeybadger_status).to eq(:skipped)
792
- end
793
-
794
- it "call log error callback with logged_to_honeybadger set to false if an error occurs while attempting to notify honeybadger" do
795
- @honeybadger_status = nil
796
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
797
- expect(Honeybadger).to receive(:notify).with(any_args) { raise "Honeybadger Notification Failure" }
798
- ExceptionHandling.log_error(exception_1)
799
- expect(@honeybadger_status).to eq(:failure)
800
- end
801
-
802
- it "call log error callback with logged_to_honeybadger set to false on unsuccessful honeybadger notification" do
803
- @honeybadger_status = nil
804
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
805
- expect(Honeybadger).to receive(:notify).with(any_args) { false }
806
- ExceptionHandling.log_error(exception_1)
807
- expect(@honeybadger_status).to eq(:failure)
808
- end
809
-
810
- it "call log error callback with logged_to_honeybadger set to true on successful honeybadger notification" do
811
- @honeybadger_status = nil
812
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
813
- expect(Honeybadger).to receive(:notify).with(any_args) { '06220c5a-b471-41e5-baeb-de247da45a56' }
814
- ExceptionHandling.log_error(exception_1)
815
- expect(@honeybadger_status).to eq(:success)
816
- end
817
- end
818
- end
819
- end
820
-
821
- class EventResponse
822
- def to_s
823
- "message from to_s!"
824
- end
825
- end
826
-
827
- it "allow sections to have data with just a to_s method" do
828
- capture_notifications
829
-
830
- ExceptionHandling.log_error("This is my RingSwitch example.") do |data|
831
- data.merge!(event_response: EventResponse.new)
832
- end
833
-
834
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
835
- expect(sent_notifications.last.enhanced_data['event_response'].to_s).to match(/message from to_s!/)
836
- end
837
- end
838
-
839
- it "return the error ID (timestamp)" do
840
- result = ExceptionHandling.log_error(RuntimeError.new("A runtime error"), "Runtime message")
841
- expect(result).to eq(ExceptionHandling.last_exception_timestamp)
842
- end
843
-
844
- it "rescue exceptions that happen in log_error" do
845
- allow(ExceptionHandling).to receive(:make_exception) { raise ArgumentError, "Bad argument" }
846
- expect(ExceptionHandling).to receive(:write_exception_to_log).with(satisfy { |ex| ex.to_s['Bad argument'] },
847
- satisfy { |context| context['ExceptionHandlingError: log_error rescued exception while logging Runtime message'] },
848
- anything)
849
- ExceptionHandling.log_error(RuntimeError.new("A runtime error"), "Runtime message")
850
- end
851
-
852
- it "rescue exceptions that happen when log_error yields" do
853
- expect(ExceptionHandling).to receive(:write_exception_to_log).with(satisfy { |ex| ex.to_s['Bad argument'] },
854
- satisfy { |context| context['Context message'] },
855
- anything,
856
- anything)
857
- ExceptionHandling.log_error(ArgumentError.new("Bad argument"), "Context message") { |_data| raise 'Error!!!' }
858
- end
859
-
860
- context "Exception Filtering" do
861
- before do
862
- filter_list = { exception1: { 'error' => "my error message" },
863
- exception2: { 'error' => "some other message", :session => "misc data" } }
864
- allow(YAML).to receive(:load_file) { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
865
-
866
- # bump modified time up to get the above filter loaded
867
- allow(File).to receive(:mtime) { incrementing_mtime }
868
- end
869
-
870
- it "handle case where filter list is not found" do
871
- allow(YAML).to receive(:load_file) { raise Errno::ENOENT, "File not found" }
872
-
873
- capture_notifications
874
-
875
- ExceptionHandling.log_error("My error message is in list")
876
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
877
- end
878
-
879
- it "log exception and suppress email when exception is on filter list" do
880
- capture_notifications
881
-
882
- ExceptionHandling.log_error("Error message is not in list")
883
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
884
-
885
- sent_notifications.clear
886
- ExceptionHandling.log_error("My error message is in list")
887
- expect(sent_notifications.size).to eq(0), sent_notifications.inspect
888
- end
889
-
890
- it "allow filtering exception on any text in exception data" do
891
- filters = { exception1: { session: "data: my extra session data" } }
892
- allow(YAML).to receive(:load_file) { ActiveSupport::HashWithIndifferentAccess.new(filters) }
893
-
894
- capture_notifications
895
-
896
- ExceptionHandling.log_error("No match here") do |data|
897
- data[:session] = {
898
- key: "@session_id",
899
- data: "my extra session data"
900
- }
901
- end
902
- expect(sent_notifications.size).to eq(0), sent_notifications.inspect
903
-
904
- ExceptionHandling.log_error("No match here") do |data|
905
- data[:session] = {
906
- key: "@session_id",
907
- data: "my extra session <no match!> data"
908
- }
909
- end
910
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
911
- end
912
-
913
- it "reload filter list on the next exception if file was modified" do
914
- capture_notifications
915
-
916
- ExceptionHandling.log_error("Error message is not in list")
917
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
918
-
919
- filter_list = { exception1: { 'error' => "Error message is not in list" } }
920
- allow(YAML).to receive(:load_file) { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
921
- allow(File).to receive(:mtime) { incrementing_mtime }
922
-
923
- sent_notifications.clear
924
- ExceptionHandling.log_error("Error message is not in list")
925
- expect(sent_notifications.size).to eq(0), sent_notifications.inspect
926
- end
927
-
928
- it "not consider filter if both error message and body do not match" do
929
- capture_notifications
930
-
931
- # error message matches, but not full text
932
- ExceptionHandling.log_error("some other message")
933
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
934
-
935
- # now both match
936
- sent_notifications.clear
937
- ExceptionHandling.log_error("some other message") do |data|
938
- data[:session] = { some_random_key: "misc data" }
939
- end
940
- expect(sent_notifications.size).to eq(0), sent_notifications.inspect
941
- end
942
-
943
- it "skip environment keys not on whitelist" do
944
- capture_notifications
945
-
946
- ExceptionHandling.log_error("some message") do |data|
947
- data[:environment] = { SERVER_PROTOCOL: "HTTP/1.0", RAILS_SECRETS_YML_CONTENTS: 'password: VERY_SECRET_PASSWORD' }
948
- end
949
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
950
-
951
- mail = sent_notifications.last
952
- environment = mail.enhanced_data['environment']
953
-
954
- expect(environment["RAILS_SECRETS_YML_CONTENTS"]).to be_nil, environment.inspect # this is not on whitelist).to be_nil
955
- expect(environment["SERVER_PROTOCOL"]).to be_truthy, environment.inspect # this is
956
- end
957
-
958
- it "omit environment defaults" do
959
- capture_notifications
960
-
961
- allow(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(anything) { |exception_info| sent_notifications << exception_info }
962
-
963
- ExceptionHandling.log_error("some message") do |data|
964
- data[:environment] = { SERVER_PORT: '80', SERVER_PROTOCOL: "HTTP/1.0" }
965
- end
966
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
967
- mail = sent_notifications.last
968
- environment = mail.enhanced_data['environment']
969
-
970
- expect(environment["SERVER_PORT"]).to be_nil, environment.inspect # this was default).to be_nil
971
- expect(environment["SERVER_PROTOCOL"]).to be_truthy, environment.inspect # this was not
972
- end
973
-
974
- it "reject the filter file if any contain all empty regexes" do
975
- filter_list = { exception1: { 'error' => "", :session => "" },
976
- exception2: { 'error' => "is not in list", :session => "" } }
977
- allow(YAML).to receive(:load_file) { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
978
- allow(File).to receive(:mtime) { incrementing_mtime }
979
-
980
- capture_notifications
981
-
982
- ExceptionHandling.log_error("Error message is not in list")
983
- expect(sent_notifications.size).to eq(1), sent_notifications.inspect
984
- end
985
-
986
- it "reload filter file if filename changes" do
987
- catalog = ExceptionHandling.exception_catalog
988
- ExceptionHandling.filter_list_filename = "./config/other_exception_filters.yml"
989
- expect(ExceptionHandling.exception_catalog).to_not eq(catalog)
990
- end
991
-
992
- context "Exception Handling Mailer" do
993
- EXPECTED_SMTP_HASH =
994
- {
995
- host: '127.0.0.1',
996
- domain: 'localhost.localdomain',
997
- from: 'server@example.com',
998
- to: 'escalation@example.com'
999
- }.freeze
1000
-
1001
- [[true, false], [true, true]].each do |em_flag, synchrony_flag|
1002
- context "eventmachine_safe = #{em_flag} && eventmachine_synchrony = #{synchrony_flag}" do
1003
- before do
1004
- ExceptionHandling.eventmachine_safe = em_flag
1005
- ExceptionHandling.eventmachine_synchrony = synchrony_flag
1006
- EventMachineStub.block = nil
1007
- set_test_const('EventMachine', EventMachineStub)
1008
- set_test_const('EventMachine::Protocols', Module.new)
1009
- set_test_const('EventMachine::DNS', Module.new)
1010
- set_test_const('EventMachine::DNS::Resolver', DNSResolvStub)
1011
- end
1012
-
1013
- after do
1014
- ExceptionHandling.eventmachine_safe = false
1015
- ExceptionHandling.eventmachine_synchrony = false
1016
- end
1017
-
1018
- it "schedule EventMachine STMP when EventMachine defined" do
1019
- ActionMailer::Base.deliveries.clear
1020
-
1021
- set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientStub)
1022
-
1023
- ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
1024
- expect(EventMachineStub.block).to be_truthy
1025
- EventMachineStub.block.call
1026
- expect(DNSResolvStub.callback_block).to be_truthy
1027
- DNSResolvStub.callback_block.call ['127.0.0.1']
1028
- expect((SmtpClientStub.send_hash & EXPECTED_SMTP_HASH.keys).map_hash { |_k, v| v.to_s }) .to eq(EXPECTED_SMTP_HASH), SmtpClientStub.send_hash.inspect
1029
- expect(SmtpClientStub.last_method).to eq((synchrony_flag ? :asend : :send))
1030
- expect(SmtpClientStub.send_hash[:content]).to match(/Exception to escalate/)
1031
- assert_emails 0, ActionMailer::Base.deliveries.*.to_s
1032
- end
1033
-
1034
- it "pass the content as a proper rfc 2822 message" do
1035
- set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientStub)
1036
- ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
1037
- expect(EventMachineStub.block).to be_truthy
1038
- EventMachineStub.block.call
1039
- expect(DNSResolvStub.callback_block).to be_truthy
1040
- DNSResolvStub.callback_block.call ['127.0.0.1']
1041
- expect(content = SmtpClientStub.send_hash[:content]).to be_truthy
1042
- expect(content).to match(/Content-Transfer-Encoding: 7bit/)
1043
- expect(content).to match(/\r\n\.\r\n\z/)
1044
- end
1045
-
1046
- it "log fatal on EventMachine STMP errback" do
1047
- ActionMailer::Base.deliveries.clear
1048
-
1049
- set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientErrbackStub)
1050
- expect(ExceptionHandling.logger).to receive(:fatal).with(/Exception to escalate/, anything)
1051
- expect(ExceptionHandling.logger).to receive(:fatal).with(/Failed to email by SMTP: "credential mismatch"/)
1052
-
1053
- ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
1054
- expect(EventMachineStub.block).to be_truthy
1055
- EventMachineStub.block.call
1056
- expect(DNSResolvStub.callback_block).to be_truthy
1057
- DNSResolvStub.callback_block.call(['127.0.0.1'])
1058
- SmtpClientErrbackStub.block.call("credential mismatch")
1059
- expect((SmtpClientErrbackStub.send_hash & EXPECTED_SMTP_HASH.keys).map_hash { |_k, v| v.to_s }).to eq(EXPECTED_SMTP_HASH), SmtpClientErrbackStub.send_hash.inspect end
1060
-
1061
- it "log fatal on EventMachine dns resolver errback" do
1062
- expect(ExceptionHandling.logger).to receive(:fatal).with(/Exception to escalate/, anything)
1063
- expect(ExceptionHandling.logger).to receive(:fatal).with(/Failed to resolv DNS for localhost: "softlayer sucks"/)
1064
-
1065
- ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
1066
- expect(EventMachineStub.block).to be_truthy
1067
- EventMachineStub.block.call
1068
- DNSResolvStub.errback_block.call("softlayer sucks")
1069
- end
1070
- end
1071
- end
1072
- end
1073
- end
1074
-
1075
- context "Exception mapping" do
1076
- before do
1077
- @data = {
1078
- environment: {
1079
- 'HTTP_HOST' => "localhost",
1080
- 'HTTP_REFERER' => "http://localhost/action/controller/instance",
1081
- },
1082
- session: {
1083
- data: {
1084
- affiliate_id: defined?(Affiliate) ? Affiliate.first.id : '1',
1085
- edit_mode: true,
1086
- advertiser_id: defined?(Advertiser) ? Advertiser.first.id : '1',
1087
- username_id: defined?(Username) ? Username.first.id : '1',
1088
- user_id: defined?(User) ? User.first.id : '1',
1089
- flash: {},
1090
- impersonated_organization_pk: 'Advertiser_1'
1091
- }
1092
- },
1093
- request: {},
1094
- backtrace: ["[GEM_ROOT]/gems/actionpack-2.1.0/lib/action_controller/filters.rb:580:in `call_filters'", "[GEM_ROOT]/gems/actionpack-2.1.0/lib/action_controller/filters.rb:601:in `run_before_filters'"],
1095
- api_key: "none",
1096
- error_class: "StandardError",
1097
- error: 'Some error message'
1098
- }
1099
- end
1100
-
1101
- it "clean backtraces" do
1102
- begin
1103
- raise "test exception"
1104
- rescue => ex
1105
- backtrace = ex.backtrace
1106
- end
1107
- result = ExceptionHandling.send(:clean_backtrace, ex).to_s
1108
- expect(backtrace).to_not eq(result)
1109
- end
1110
-
1111
- it "return entire backtrace if cleaned is emtpy" do
1112
- begin
1113
- backtrace = ["/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/relation/finder_methods.rb:312:in `find_with_ids'",
1114
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/relation/finder_methods.rb:107:in `find'",
1115
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/querying.rb:5:in `__send__'",
1116
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/querying.rb:5:in `find'",
1117
- "/Library/Ruby/Gems/1.8/gems/shoulda-context-1.0.2/lib/shoulda/context/context.rb:398:in `call'",
1118
- "/Library/Ruby/Gems/1.8/gems/shoulda-context-1.0.2/lib/shoulda/context/context.rb:398:in `test: Exception mapping should return entire backtrace if cleaned is emtpy. '",
1119
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:72:in `__send__'",
1120
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:72:in `run'",
1121
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:447:in `_run__1913317170__setup__4__callbacks'",
1122
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:405:in `send'",
1123
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:405:in `__run_callback'",
1124
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:385:in `_run_setup_callbacks'",
1125
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:81:in `send'",
1126
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:81:in `run_callbacks'",
1127
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:70:in `run'",
1128
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:34:in `run'",
1129
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `each'",
1130
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `run'",
1131
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnermediator.rb:46:in `old_run_suite'",
1132
- "(eval):12:in `run_suite'",
1133
- "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:93:in `send'",
1134
- "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:93:in `start_mediator'",
1135
- "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:81:in `start'",
1136
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnerutilities.rb:29:in `run'",
1137
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/autorunner.rb:12:in `run'",
1138
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit.rb:279",
1139
- "-e:1"]
1140
-
1141
- module ::Rails
1142
- class BacktraceCleaner
1143
- def clean(_backtrace)
1144
- []
1145
- end
1146
- end
1147
- end
1148
-
1149
- rails = double(Rails)
1150
- expect(rails).to receive(:backtrace_cleaner) { Rails::BacktraceCleaner.new }
1151
- rails.backtrace_cleaner
1152
-
1153
- ex = Exception.new
1154
- ex.set_backtrace(backtrace)
1155
- result = ExceptionHandling.send(:clean_backtrace, ex)
1156
- expect(result).to eq(backtrace)
1157
- ensure
1158
- Object.send(:remove_const, :Rails)
1159
- end
1160
- end
1161
- end
1162
-
1163
- context "log_perodically" do
1164
- before do
1165
- Time.now_override = Time.now # Freeze time
1166
- ExceptionHandling.logger.clear
1167
- end
1168
-
1169
- after do
1170
- Time.now_override = nil
1171
- end
1172
-
1173
- it "take in additional logging context and pass them to the logger" do
1174
- ExceptionHandling.log_periodically(:test_context_with_periodic, 30.minutes, "this will be written", service_name: 'exception_handling')
1175
- expect(logged_excluding_reload_filter.last[:context]).to_not be_empty
1176
- expect(logged_excluding_reload_filter.last[:context]).to eq({ service_name: 'exception_handling' })
1177
- end
1178
-
1179
- it "log immediately when we are expected to log" do
1180
- ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will be written")
1181
- expect(logged_excluding_reload_filter.size).to eq(1)
1182
-
1183
- Time.now_override = Time.now + 5.minutes
1184
- ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will not be written")
1185
- expect(logged_excluding_reload_filter.size).to eq(1)
1186
-
1187
- ExceptionHandling.log_periodically(:test_another_periodic_exception, 30.minutes, "this will be written")
1188
- expect(logged_excluding_reload_filter.size).to eq(2)
1189
-
1190
- Time.now_override = Time.now + 26.minutes
1191
-
1192
- ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will be written")
1193
- expect(logged_excluding_reload_filter.size).to eq(3)
1194
- end
1195
- end
1196
- end
1197
-
1198
- context "ExceptionHandling < 3.0 " do
1199
- it "should return a deprecation warning" do
1200
- ExceptionHandling.production_support_recipients = "prodsupport@example.com"
1201
- expect { ExceptionHandling.escalate_to_production_support("blah", "invoca@example.com") }
1202
- .to output(/DEPRECATION WARNING: escalate_to_production_support is deprecated and will be removed from ExceptionHandling 3.0/).to_stderr
1203
- end
1204
-
1205
- it "should return a deprecation warning" do
1206
- expect { ExceptionHandling.escalate_error("blah", "invoca@example.com") }
1207
- .to output(/DEPRECATION WARNING: escalate_error is deprecated and will be removed from ExceptionHandling 3.0/).to_stderr
1208
- end
1209
-
1210
- it "should return a deprecation warning" do
1211
- expect { ExceptionHandling.escalate_warning("blah", "invoca@example.com") }
1212
- .to output(/DEPRECATION WARNING: escalate_warning is deprecated and will be removed from ExceptionHandling 3.0/).to_stderr
1213
- end
1214
-
1215
- it "should return a deprecation warning" do
1216
- expect { ExceptionHandling.ensure_escalation("blah", "invoca@example.com") }
1217
- .to output(/DEPRECATION WARNING: ensure_escalation is deprecated and will be removed from ExceptionHandling 3.0/).to_stderr
1218
- end
1219
- end
1220
-
1221
- private
1222
-
1223
- def logged_excluding_reload_filter
1224
- ExceptionHandling.logger.logged.select { |l| l[:message] !~ /Reloading filter list/ }
1225
- end
1226
-
1227
- def incrementing_mtime
1228
- @mtime ||= Time.now
1229
- @mtime += 1.day
1230
- end
1231
-
1232
- def exception_1
1233
- @exception_1 ||=
1234
- begin
1235
- raise StandardError, "Exception 1"
1236
- rescue => ex
1237
- ex
1238
- end
1239
- end
1240
-
1241
- def exception_2
1242
- @exception_2 ||=
1243
- begin
1244
- raise StandardError, "Exception 2"
1245
- rescue => ex
1246
- ex
1247
- end
1248
- end
1249
- end