exception_handling 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,717 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class ExceptionHandlingTest < ActiveSupport::TestCase
4
+
5
+ def dont_stub_log_error
6
+ true
7
+ end
8
+
9
+ module EventMachineStub
10
+ class << self
11
+ attr_accessor :block
12
+
13
+ def schedule(&block)
14
+ @block = block
15
+ end
16
+ end
17
+ end
18
+
19
+ class SmtpClientStub
20
+ class << self
21
+ attr_reader :block
22
+ attr_reader :last_method
23
+
24
+ def errback(&block)
25
+ @block = block
26
+ end
27
+
28
+ def send_hash
29
+ @send_hash ||= {}
30
+ end
31
+
32
+ def send(hash)
33
+ @last_method = :send
34
+ send_hash.clear
35
+ send_hash.merge!(hash)
36
+ self
37
+ end
38
+
39
+ def asend(hash)
40
+ send(hash)
41
+ @last_method = :asend
42
+ self
43
+ end
44
+ end
45
+ end
46
+
47
+ class SmtpClientErrbackStub < SmtpClientStub
48
+ end
49
+
50
+ context "configuration" do
51
+ def append_organization_info(data)
52
+ begin
53
+ data[:user_details] = {}
54
+ data[:user_details][:username] = "CaryP"
55
+ data[:user_details][:organization] = "Invoca Engineering Dept."
56
+ rescue Exception => e
57
+ # don't let these out!
58
+ end
59
+ end
60
+
61
+ def log_error_callback(data, ex)
62
+ @fail_count += 1
63
+ end
64
+
65
+ def log_error_callback_with_failure(data, ex)
66
+ raise "this should be rescued"
67
+ end
68
+
69
+ setup do
70
+ @fail_count = 0
71
+ end
72
+
73
+ should "support a custom_data_hook" do
74
+ ExceptionHandling.custom_data_hook = method(:append_organization_info)
75
+ ExceptionHandling.ensure_safe("mooo") { raise "Some BS" }
76
+ assert_match(/Invoca Engineering Dept./, ActionMailer::Base.deliveries[-1].body.to_s)
77
+ ExceptionHandling.custom_data_hook = nil
78
+ end
79
+
80
+ should "support a log_error hook" do
81
+ ExceptionHandling.post_log_error_hook = method(:log_error_callback)
82
+ ExceptionHandling.ensure_safe("mooo") { raise "Some BS" }
83
+ assert_equal 1, @fail_count
84
+ ExceptionHandling.post_log_error_hook = nil
85
+ end
86
+
87
+ should "support rescue exceptions from a log_error hook" do
88
+ ExceptionHandling.post_log_error_hook = method(:log_error_callback_with_failure)
89
+ assert_nothing_raised { ExceptionHandling.ensure_safe("mooo") { raise "Some BS" } }
90
+ assert_equal 0, @fail_count
91
+ ExceptionHandling.post_log_error_hook = nil
92
+ end
93
+ end
94
+
95
+ context "Exception Handling" do
96
+ setup do
97
+ ActionMailer::Base.deliveries.clear
98
+ ExceptionHandling.send(:clear_exception_summary)
99
+ end
100
+
101
+ context "exception filter parsing and loading" do
102
+ should "happen without an error" do
103
+ stub(File).mtime { incrementing_mtime }
104
+ exception_filters = ExceptionHandling.send( :exception_filters )
105
+ assert( exception_filters.is_a?( ExceptionHandling::ExceptionFilters ) )
106
+ assert_nothing_raised "Loading the exception filter should not raise" do
107
+ exception_filters.send :load_file
108
+ end
109
+ assert !exception_filters.filtered?( "Scott says unlikely to ever match" )
110
+ end
111
+ end
112
+
113
+ context "ExceptionHandling.ensure_safe" do
114
+ should "log an exception if an exception is raised." do
115
+ mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/)
116
+ ExceptionHandling.ensure_safe { raise ArgumentError.new("blah") }
117
+ end
118
+
119
+ should "should not log an exception if an exception is not raised." do
120
+ dont_allow(ExceptionHandling.logger).fatal
121
+ ExceptionHandling.ensure_safe { ; }
122
+ end
123
+
124
+ should "return its value if used during an assignment" do
125
+ dont_allow(ExceptionHandling.logger).fatal
126
+ b = ExceptionHandling.ensure_safe { 5 }
127
+ assert_equal 5, b
128
+ end
129
+
130
+ should "return nil if an exception is raised during an assignment" do
131
+ mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/)
132
+ b = ExceptionHandling.ensure_safe { raise ArgumentError.new("blah") }
133
+ assert_equal nil, b
134
+ end
135
+
136
+ should "allow a message to be appended to the error when logged." do
137
+ mock(ExceptionHandling.logger).fatal(/mooo \(blah\):\n.*exception_handling_test\.rb/)
138
+ b = ExceptionHandling.ensure_safe("mooo") { raise ArgumentError.new("blah") }
139
+ assert_nil b
140
+ end
141
+
142
+ should "only rescue StandardError and descendents" do
143
+ assert_raise(Exception) { ExceptionHandling.ensure_safe("mooo") { raise Exception } }
144
+
145
+ mock(ExceptionHandling.logger).fatal(/mooo \(blah\):\n.*exception_handling_test\.rb/)
146
+
147
+ b = ExceptionHandling.ensure_safe("mooo") { raise StandardError.new("blah") }
148
+ assert_nil b
149
+ end
150
+ end
151
+
152
+ context "ExceptionHandling.ensure_completely_safe" do
153
+ should "log an exception if an exception is raised." do
154
+ mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/)
155
+ ExceptionHandling.ensure_completely_safe { raise ArgumentError.new("blah") }
156
+ end
157
+
158
+ should "should not log an exception if an exception is not raised." do
159
+ mock(ExceptionHandling.logger).fatal.times(0)
160
+ ExceptionHandling.ensure_completely_safe { ; }
161
+ end
162
+
163
+ should "return its value if used during an assignment" do
164
+ mock(ExceptionHandling.logger).fatal.times(0)
165
+ b = ExceptionHandling.ensure_completely_safe { 5 }
166
+ assert_equal 5, b
167
+ end
168
+
169
+ should "return nil if an exception is raised during an assignment" do
170
+ mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/) { nil }
171
+ b = ExceptionHandling.ensure_completely_safe { raise ArgumentError.new("blah") }
172
+ assert_equal nil, b
173
+ end
174
+
175
+ should "allow a message to be appended to the error when logged." do
176
+ mock(ExceptionHandling.logger).fatal(/mooo \(blah\):\n.*exception_handling_test\.rb/)
177
+ b = ExceptionHandling.ensure_completely_safe("mooo") { raise ArgumentError.new("blah") }
178
+ assert_nil b
179
+ end
180
+
181
+ should "rescue any instance or child of Exception" do
182
+ mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/)
183
+ ExceptionHandling::ensure_completely_safe { raise Exception.new("blah") }
184
+ end
185
+
186
+ should "not rescue the special exceptions that Ruby uses" do
187
+ [SystemExit, SystemStackError, NoMemoryError, SecurityError].each do |exception|
188
+ assert_raise exception do
189
+ ExceptionHandling.ensure_completely_safe do
190
+ raise exception.new
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ context "ExceptionHandling.ensure_escalation" do
198
+ should "log the exception as usual and send the proper email" do
199
+ assert_equal 0, ActionMailer::Base.deliveries.count
200
+ mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/)
201
+ ExceptionHandling.ensure_escalation( "Favorite Feature") { raise ArgumentError.new("blah") }
202
+ assert_equal 2, ActionMailer::Base.deliveries.count
203
+ email = ActionMailer::Base.deliveries.last
204
+ assert_equal "#{ExceptionHandling.email_environment} Escalation: Favorite Feature", email.subject
205
+ assert_match 'ArgumentError: blah', email.body.to_s
206
+ assert_match ExceptionHandling.last_exception_timestamp.to_s, email.body.to_s
207
+ end
208
+
209
+ should "should not escalate if an exception is not raised." do
210
+ assert_equal 0, ActionMailer::Base.deliveries.count
211
+ dont_allow(ExceptionHandling.logger).fatal
212
+ ExceptionHandling.ensure_escalation('Ignored') { ; }
213
+ assert_equal 0, ActionMailer::Base.deliveries.count
214
+ end
215
+
216
+ should "log if the escalation email can not be sent" do
217
+ any_instance_of(Mail::Message) do |message|
218
+ mock(message).deliver
219
+ mock(message).deliver { raise RuntimeError.new, "Delivery Error" }
220
+ end
221
+ mock(ExceptionHandling.logger) do |logger|
222
+ logger.fatal(/first_test_exception/)
223
+ logger.fatal(/safe_email_deliver .*Delivery Error/)
224
+ end
225
+ ExceptionHandling.ensure_escalation("Not Used") { raise ArgumentError.new("first_test_exception") }
226
+ assert_equal 0, ActionMailer::Base.deliveries.count
227
+ end
228
+ end
229
+
230
+ context "exception timestamp" do
231
+ setup do
232
+ Time.now_override = Time.parse( '1986-5-21 4:17 am UTC' )
233
+ end
234
+
235
+ should "include the timestamp when the exception is logged" do
236
+ mock(ExceptionHandling.logger).fatal(/\(Error:517033020\) ArgumentError mooo \(blah\):\n.*exception_handling_test\.rb/)
237
+ b = ExceptionHandling.ensure_safe("mooo") { raise ArgumentError.new("blah") }
238
+ assert_nil b
239
+
240
+ assert_equal 517033020, ExceptionHandling.last_exception_timestamp
241
+
242
+ assert_emails 1
243
+ assert_match(/517033020/, ActionMailer::Base.deliveries[-1].body.to_s)
244
+ end
245
+ end
246
+
247
+ should "send just one copy of exceptions that don't repeat" do
248
+ ExceptionHandling.log_error(exception_1)
249
+ ExceptionHandling.log_error(exception_2)
250
+ assert_emails 2
251
+ assert_match(/Exception 1/, ActionMailer::Base.deliveries[-2].subject)
252
+ assert_match(/Exception 2/, ActionMailer::Base.deliveries[-1].subject)
253
+ end
254
+
255
+ should "only send 5 of a repeated error" do
256
+ assert_emails 5 do
257
+ 10.times do
258
+ ExceptionHandling.log_error(exception_1)
259
+ end
260
+ end
261
+ end
262
+
263
+ should "only send 5 of a repeated error but don't send summary if 6th is different" do
264
+ assert_emails 5 do
265
+ 5.times do
266
+ ExceptionHandling.log_error(exception_1)
267
+ end
268
+ end
269
+ assert_emails 1 do
270
+ ExceptionHandling.log_error(exception_2)
271
+ end
272
+ end
273
+
274
+ should "send the summary when the error is encountered an hour after the first occurrence" do
275
+ assert_emails 5 do # 5 exceptions, 4 summarized
276
+ 9.times do |t|
277
+ ExceptionHandling.log_error(exception_1)
278
+ end
279
+ end
280
+ Time.now_override = 2.hours.from_now
281
+ assert_emails 1 do # 1 summary (4 + 1 = 5) after 2 hours
282
+ ExceptionHandling.log_error(exception_1)
283
+ end
284
+ assert_match(/\[5 SUMMARIZED\]/, ActionMailer::Base.deliveries.last.subject)
285
+ assert_match(/This exception occurred 5 times since/, ActionMailer::Base.deliveries.last.body.to_s)
286
+
287
+ assert_emails 0 do # still summarizing...
288
+ 7.times do
289
+ ExceptionHandling.log_error(exception_1)
290
+ end
291
+ end
292
+
293
+ Time.now_override = 3.hours.from_now
294
+
295
+ assert_emails 1 + 2 do # 1 summary and 2 new
296
+ 2.times do
297
+ ExceptionHandling.log_error(exception_2)
298
+ end
299
+ end
300
+ assert_match(/\[7 SUMMARIZED\]/, ActionMailer::Base.deliveries[-3].subject)
301
+ assert_match(/This exception occurred 7 times since/, ActionMailer::Base.deliveries[-3].body.to_s)
302
+ end
303
+
304
+ should "send the summary if a summary is available, but not sent when another exception comes up" do
305
+ assert_emails 5 do # 5 to start summarizing
306
+ 6.times do
307
+ ExceptionHandling.log_error(exception_1)
308
+ end
309
+ end
310
+
311
+ assert_emails 1 + 1 do # 1 summary of previous, 1 from new exception
312
+ ExceptionHandling.log_error(exception_2)
313
+ end
314
+
315
+ assert_match(/\[1 SUMMARIZED\]/, ActionMailer::Base.deliveries[-2].subject)
316
+ assert_match(/This exception occurred 1 times since/, ActionMailer::Base.deliveries[-2].body.to_s)
317
+
318
+ assert_emails 5 do # 5 to start summarizing
319
+ 10.times do
320
+ ExceptionHandling.log_error(exception_1)
321
+ end
322
+ end
323
+
324
+ assert_emails 0 do # still summarizing
325
+ 11.times do
326
+ ExceptionHandling.log_error(exception_1)
327
+ end
328
+ end
329
+ end
330
+
331
+ class EventResponse
332
+ def to_s
333
+ "message from to_s!"
334
+ end
335
+ end
336
+
337
+ should "allow sections to have data with just a to_s method" do
338
+ ExceptionHandling.log_error("This is my RingSwitch example. Log, don't email!") do |data|
339
+ data.merge!(:event_response => EventResponse.new)
340
+ end
341
+ assert_emails 1
342
+ assert_match(/message from to_s!/, ActionMailer::Base.deliveries.last.body.to_s)
343
+ end
344
+ end
345
+
346
+ should "rescue exceptions that happen in log_error" do
347
+ stub(ExceptionHandling).make_exception { raise ArgumentError.new("Bad argument") }
348
+ mock(ExceptionHandling).write_exception_to_log(satisfy { |ex| ex.to_s['Bad argument'] },
349
+ satisfy { |context| context['ExceptionHandling.log_error rescued exception while logging Runtime message'] },
350
+ anything)
351
+ stub($stderr).puts
352
+ ExceptionHandling.log_error(RuntimeError.new("A runtime error"), "Runtime message")
353
+ end
354
+
355
+ should "rescue exceptions that happen when log_error yields" do
356
+ mock(ExceptionHandling).write_exception_to_log(satisfy { |ex| ex.to_s['Bad argument'] },
357
+ satisfy { |context| context['Context message'] },
358
+ anything)
359
+ ExceptionHandling.log_error(ArgumentError.new("Bad argument"), "Context message") { |data| raise 'Error!!!' }
360
+ end
361
+
362
+ context "Exception Filtering" do
363
+ setup do
364
+ filter_list = { :exception1 => { 'error' => "my error message" },
365
+ :exception2 => { 'error' => "some other message", :session => "misc data" } }
366
+ stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
367
+
368
+ # bump modified time up to get the above filter loaded
369
+ stub(File).mtime { incrementing_mtime }
370
+ end
371
+
372
+ should "handle case where filter list is not found" do
373
+ stub(YAML).load_file { raise Errno::ENOENT.new("File not found") }
374
+
375
+ ActionMailer::Base.deliveries.clear
376
+ ExceptionHandling.log_error( "My error message is in list" )
377
+ assert_emails 1
378
+ end
379
+
380
+ should "log exception and suppress email when exception is on filter list" do
381
+ ActionMailer::Base.deliveries.clear
382
+ ExceptionHandling.log_error( "Error message is not in list" )
383
+ assert_emails 1
384
+
385
+ ActionMailer::Base.deliveries.clear
386
+ ExceptionHandling.log_error( "My error message is in list" )
387
+ assert_emails 0
388
+ end
389
+
390
+ should "allow filtering exception on any text in exception data" do
391
+ filters = { :exception1 => { :session => "data: my extra session data" } }
392
+ stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filters) }
393
+
394
+ ActionMailer::Base.deliveries.clear
395
+ ExceptionHandling.log_error( "No match here" ) do |data|
396
+ data[:session] = {
397
+ :key => "@session_id",
398
+ :data => "my extra session data"
399
+ }
400
+ end
401
+ assert_emails 0
402
+
403
+ ActionMailer::Base.deliveries.clear
404
+ ExceptionHandling.log_error( "No match here" ) do |data|
405
+ data[:session] = {
406
+ :key => "@session_id",
407
+ :data => "my extra session <no match!> data"
408
+ }
409
+ end
410
+ assert_emails 1, ActionMailer::Base.deliveries.*.body.*.inspect
411
+ end
412
+
413
+ should "reload filter list on the next exception if file was modified" do
414
+ ActionMailer::Base.deliveries.clear
415
+ ExceptionHandling.log_error( "Error message is not in list" )
416
+ assert_emails 1
417
+
418
+ filter_list = { :exception1 => { 'error' => "Error message is not in list" } }
419
+ stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
420
+ stub(File).mtime { incrementing_mtime }
421
+
422
+ ActionMailer::Base.deliveries.clear
423
+ ExceptionHandling.log_error( "Error message is not in list" )
424
+ assert_emails 0, ActionMailer::Base.deliveries.*.body.*.inspect
425
+ end
426
+
427
+ should "not consider filter if both error message and body do not match" do
428
+ # error message matches, but not full text
429
+ ActionMailer::Base.deliveries.clear
430
+ ExceptionHandling.log_error( "some other message" )
431
+ assert_emails 1, ActionMailer::Base.deliveries.*.body.*.inspect
432
+
433
+ # now both match
434
+ ActionMailer::Base.deliveries.clear
435
+ ExceptionHandling.log_error( "some other message" ) do |data|
436
+ data[:session] = {:some_random_key => "misc data"}
437
+ end
438
+ assert_emails 0, ActionMailer::Base.deliveries.*.body.*.inspect
439
+ end
440
+
441
+ should "skip environment keys not on whitelist" do
442
+ ActionMailer::Base.deliveries.clear
443
+ ExceptionHandling.log_error( "some message" ) do |data|
444
+ data[:environment] = { :SERVER_PROTOCOL => "HTTP/1.0", :RAILS_SECRETS_YML_CONTENTS => 'password: VERY_SECRET_PASSWORD' }
445
+ end
446
+ assert_emails 1, ActionMailer::Base.deliveries.*.body.*.inspect
447
+ mail = ActionMailer::Base.deliveries.last
448
+ assert_nil mail.body.to_s["RAILS_SECRETS_YML_CONTENTS"], mail.body.to_s # this is not on whitelist
449
+ assert mail.body.to_s["SERVER_PROTOCOL: HTTP/1.0" ], mail.body.to_s # this is
450
+ end
451
+
452
+ should "omit environment defaults" do
453
+ ActionMailer::Base.deliveries.clear
454
+ ExceptionHandling.log_error( "some message" ) do |data|
455
+ data[:environment] = {:SERVER_PORT => '80', :SERVER_PROTOCOL => "HTTP/1.0"}
456
+ end
457
+ assert_emails 1, ActionMailer::Base.deliveries.*.body.*.inspect
458
+ mail = ActionMailer::Base.deliveries.last
459
+ assert_nil mail.body.to_s["SERVER_PORT" ], mail.body.to_s # this was default
460
+ assert mail.body.to_s["SERVER_PROTOCOL: HTTP/1.0"], mail.body.to_s # this was not
461
+ end
462
+
463
+ should "reject the filter file if any contain all empty regexes" do
464
+ filter_list = { :exception1 => { 'error' => "", :session => "" },
465
+ :exception2 => { 'error' => "is not in list", :session => "" } }
466
+ stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
467
+ stub(File).mtime { incrementing_mtime }
468
+
469
+ ActionMailer::Base.deliveries.clear
470
+ ExceptionHandling.log_error( "Error message is not in list" )
471
+ assert_emails 1, ActionMailer::Base.deliveries.*.inspect
472
+ end
473
+
474
+ context "Exception Handling Mailer" do
475
+ should "create email" do
476
+ ExceptionHandling.log_error(exception_1) do |data|
477
+ data[:request] = { :params => {:id => 10993}, :url => "www.ringrevenue.com" }
478
+ data[:session] = { :key => "DECAFE" }
479
+ end
480
+ assert_emails 1, ActionMailer::Base.deliveries.*.inspect
481
+ assert mail = ActionMailer::Base.deliveries.last
482
+ assert_equal ['exceptions@example.com'], mail.to
483
+ assert_equal ['server@example.com'].to_s, mail.from.to_s
484
+ assert_match /Exception 1/, mail.to_s
485
+ assert_match /key: DECAFE/, mail.to_s
486
+ assert_match /id: 10993/, mail.to_s
487
+ end
488
+
489
+ EXPECTED_SMTP_HASH =
490
+ {
491
+ :host => 'localhost',
492
+ :domain => 'localhost.localdomain',
493
+ :from => 'server@example.com',
494
+ :to => 'exceptions@example.com'
495
+ }
496
+
497
+ [[true, false], [true, true]].each do |em_flag, synchrony_flag|
498
+ context "eventmachine_safe = #{em_flag} && eventmachine_synchrony = #{synchrony_flag}" do
499
+ setup do
500
+ ExceptionHandling.eventmachine_safe = em_flag
501
+ ExceptionHandling.eventmachine_synchrony = synchrony_flag
502
+ EventMachineStub.block = nil
503
+ set_test_const('EventMachine', EventMachineStub)
504
+ set_test_const('EventMachine::Protocols', Module.new)
505
+ end
506
+
507
+ teardown do
508
+ ExceptionHandling.eventmachine_safe = false
509
+ ExceptionHandling.eventmachine_synchrony = false
510
+ end
511
+
512
+ should "schedule EventMachine STMP when EventMachine defined" do
513
+ set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientStub)
514
+
515
+ ExceptionHandling.log_error(exception_1)
516
+ assert EventMachineStub.block
517
+ EventMachineStub.block.call
518
+ assert_equal_with_diff EXPECTED_SMTP_HASH, (SmtpClientStub.send_hash & EXPECTED_SMTP_HASH.keys).map_hash { |k,v| v.to_s }, SmtpClientStub.send_hash.inspect
519
+ assert_equal((synchrony_flag ? :asend : :send), SmtpClientStub.last_method)
520
+ assert_match(/Exception 1/, SmtpClientStub.send_hash[:content])
521
+ assert_emails 0, ActionMailer::Base.deliveries.*.to_s
522
+ end
523
+
524
+ should "pass the content as a proper rfc 2822 message" do
525
+ set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientStub)
526
+ ExceptionHandling.log_error(exception_1)
527
+ assert EventMachineStub.block
528
+ EventMachineStub.block.call
529
+ assert content = SmtpClientStub.send_hash[:content]
530
+ assert_match(/Content-Transfer-Encoding: 7bit/, content)
531
+ assert_match(/<\/html>\r\n\.\r\n\z/, content)
532
+ end
533
+
534
+ should "log fatal on EventMachine STMP errback" do
535
+ assert_emails 0, ActionMailer::Base.deliveries.*.to_s
536
+ set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientErrbackStub)
537
+ mock(ExceptionHandling.logger).fatal(/Exception 1/)
538
+ mock(ExceptionHandling.logger).fatal(/Failed to email by SMTP: "credential mismatch"/)
539
+
540
+ ExceptionHandling.log_error(exception_1)
541
+ assert EventMachineStub.block
542
+ EventMachineStub.block.call
543
+ SmtpClientErrbackStub.block.call("credential mismatch")
544
+ assert_equal_with_diff EXPECTED_SMTP_HASH, (SmtpClientErrbackStub.send_hash & EXPECTED_SMTP_HASH.keys).map_hash { |k,v| v.to_s }, SmtpClientErrbackStub.send_hash.inspect
545
+ #assert_emails 0, ActionMailer::Base.deliveries.*.to_s
546
+ end
547
+ end
548
+ end
549
+ end
550
+
551
+ should "truncate email subject" do
552
+ ActionMailer::Base.deliveries.clear
553
+ text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLM".split('').join("123456789")
554
+ begin
555
+ raise text
556
+ rescue => ex
557
+ ExceptionHandling.log_error( ex )
558
+ end
559
+ assert_emails 1, ActionMailer::Base.deliveries.*.inspect
560
+ mail = ActionMailer::Base.deliveries.last
561
+ subject = "#{ExceptionHandling.email_environment} exception: RuntimeError: " + text
562
+ assert_equal subject[0,300], mail.subject
563
+ end
564
+ end
565
+
566
+ context "Exception mapping" do
567
+ setup do
568
+ @data = {
569
+ :environment=>{
570
+ 'HTTP_HOST' => "localhost",
571
+ 'HTTP_REFERER' => "http://localhost/action/controller/instance",
572
+ },
573
+ :session=>{
574
+ :data=>{
575
+ :affiliate_id=> defined?(Affiliate) ? Affiliate.first.id : '1',
576
+ :edit_mode=> true,
577
+ :advertiser_id=> defined?(Advertiser) ? Advertiser.first.id : '1',
578
+ :username_id=> defined?(Username) ? Username.first.id : '1',
579
+ :user_id=> defined?(User) ? User.first.id : '1',
580
+ :flash=>{},
581
+ :impersonated_organization_pk=> 'Advertiser_1'
582
+ }
583
+ },
584
+ :request=>{},
585
+ :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'"],
586
+ :api_key=>"none",
587
+ :error_class=>"StandardError",
588
+ :error=>'Some error message'
589
+ }
590
+ end
591
+
592
+ should "clean backtraces" do
593
+ begin
594
+ raise "test exception"
595
+ rescue => ex
596
+ backtrace = ex.backtrace
597
+ end
598
+ result = ExceptionHandling.send(:clean_backtrace, ex).to_s
599
+ assert_not_equal result, backtrace
600
+ end
601
+
602
+ should "return entire backtrace if cleaned is emtpy" do
603
+ backtrace = ["/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/relation/finder_methods.rb:312:in `find_with_ids'",
604
+ "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/relation/finder_methods.rb:107:in `find'",
605
+ "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/querying.rb:5:in `__send__'",
606
+ "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/querying.rb:5:in `find'",
607
+ "/Library/Ruby/Gems/1.8/gems/shoulda-context-1.0.2/lib/shoulda/context/context.rb:398:in `call'",
608
+ "/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. '",
609
+ "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:72:in `__send__'",
610
+ "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:72:in `run'",
611
+ "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:447:in `_run__1913317170__setup__4__callbacks'",
612
+ "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:405:in `send'",
613
+ "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:405:in `__run_callback'",
614
+ "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:385:in `_run_setup_callbacks'",
615
+ "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:81:in `send'",
616
+ "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:81:in `run_callbacks'",
617
+ "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:70:in `run'",
618
+ "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:34:in `run'",
619
+ "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `each'",
620
+ "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `run'",
621
+ "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnermediator.rb:46:in `old_run_suite'",
622
+ "(eval):12:in `run_suite'",
623
+ "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:93:in `send'",
624
+ "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:93:in `start_mediator'",
625
+ "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:81:in `start'",
626
+ "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnerutilities.rb:29:in `run'",
627
+ "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/autorunner.rb:12:in `run'",
628
+ "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit.rb:279",
629
+ "-e:1"]
630
+ ex = Exception.new
631
+ ex.set_backtrace(backtrace)
632
+ result = ExceptionHandling.send(:clean_backtrace, ex)
633
+ assert_equal backtrace, result
634
+ end
635
+
636
+ should "clean params" do
637
+ p = {'password' => 'apple', 'username' => 'sam' }
638
+ ExceptionHandling.send( :clean_params, p )
639
+ assert_equal "[FILTERED]", p['password']
640
+ assert_equal 'sam', p['username']
641
+ end
642
+ end
643
+
644
+ context "log_perodically" do
645
+ setup do
646
+ Time.now_override = Time.now # Freeze time
647
+ ExceptionHandling.logger.clear
648
+ end
649
+
650
+ teardown do
651
+ Time.now_override = nil
652
+ end
653
+
654
+ should "log immediately when we are expected to log" do
655
+ logger_stub = ExceptionHandling.logger
656
+
657
+ ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will be written")
658
+ assert_equal 1, logger_stub.logged.size
659
+
660
+ Time.now_override = Time.now + 5.minutes
661
+ ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will not be written")
662
+ assert_equal 1, logger_stub.logged.size
663
+
664
+ ExceptionHandling.log_periodically(:test_another_periodic_exception, 30.minutes, "this will be written")
665
+ assert_equal 2, logger_stub.logged.size
666
+
667
+ Time.now_override = Time.now + 26.minutes
668
+
669
+ ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will be written")
670
+ assert_equal 3, logger_stub.logged.size
671
+ end
672
+ end
673
+
674
+ context "Errplane" do
675
+ module ErrplaneStub
676
+ end
677
+
678
+ setup do
679
+ set_test_const('Errplane', ErrplaneStub)
680
+ end
681
+
682
+ should "forward exceptions" do
683
+ mock(Errplane).transmit(exception_1, anything)
684
+ ExceptionHandling.log_error(exception_1, "context")
685
+ end
686
+
687
+ should "not forward warnings" do
688
+ stub(Errplane).transmit.times(0)
689
+ ExceptionHandling.log_warning("warning message")
690
+ end
691
+ end
692
+
693
+ private
694
+
695
+ def incrementing_mtime
696
+ @mtime ||= Time.now
697
+ @mtime += 1.day
698
+ end
699
+
700
+ def exception_1
701
+ @ex1 ||=
702
+ begin
703
+ raise StandardError, "Exception 1"
704
+ rescue => ex
705
+ ex
706
+ end
707
+ end
708
+
709
+ def exception_2
710
+ @ex2 ||=
711
+ begin
712
+ raise StandardError, "Exception 2"
713
+ rescue => ex
714
+ ex
715
+ end
716
+ end
717
+ end