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.
@@ -1,15 +1,14 @@
1
1
  require 'timeout'
2
2
  require 'active_support'
3
3
  require 'active_support/core_ext/hash'
4
- require "#{File.dirname(__FILE__)}/exception_handling_mailer"
5
4
 
6
- EXCEPTION_HANDLING_MAILER_SEND_MAIL = true unless defined?(EXCEPTION_HANDLING_MAILER_SEND_MAIL)
5
+ require 'invoca/utils'
7
6
 
8
- _ = ActiveSupport::HashWithIndifferentAccess
7
+ require "exception_handling/mailer"
8
+ require "exception_handling/methods"
9
+ require "exception_handling/log_stub_error"
9
10
 
10
- if defined?(EVENTMACHINE_EXCEPTION_HANDLING) && EVENTMACHINE_EXCEPTION_HANDLING
11
- require 'em/protocols/smtpclient'
12
- end
11
+ _ = ActiveSupport::HashWithIndifferentAccess
13
12
 
14
13
  module ExceptionHandling # never included
15
14
 
@@ -20,9 +19,7 @@ module ExceptionHandling # never included
20
19
  SUMMARY_THRESHOLD = 5
21
20
  SUMMARY_PERIOD = 60*60 # 1.hour
22
21
 
23
-
24
22
  SECTIONS = [:request, :session, :environment, :backtrace, :event_response]
25
- EXCEPTION_FILTER_LIST_PATH = "#{defined?(Rails) ? Rails.root : '.'}/config/exception_filters.yml"
26
23
 
27
24
  ENVIRONMENT_WHITELIST = [
28
25
  /^HTTP_/,
@@ -71,19 +68,77 @@ EOF
71
68
 
72
69
  AUTHENTICATION_HEADERS = ['HTTP_AUTHORIZATION','X-HTTP_AUTHORIZATION','X_HTTP_AUTHORIZATION','REDIRECT_X_HTTP_AUTHORIZATION']
73
70
 
74
- @logger = Rails.logger if defined?(Rails)
75
-
76
-
77
71
  class << self
78
- attr_accessor :email_environment
72
+
73
+ #
74
+ # required settings
75
+ #
79
76
  attr_accessor :server_name
80
77
  attr_accessor :sender_address
81
78
  attr_accessor :exception_recipients
79
+ attr_accessor :logger
80
+
81
+ def server_name
82
+ @server_name or raise ArgumentError, "You must assign a value to #{self.name}.server_name"
83
+ end
84
+
85
+ def sender_address
86
+ @sender_address or raise ArgumentError, "You must assign a value to #{self.name}.sender_address"
87
+ end
88
+
89
+ def exception_recipients
90
+ @exception_recipients or raise ArgumentError, "You must assign a value to #{self.name}.exception_recipients"
91
+ end
92
+
93
+ def logger
94
+ @logger or raise ArgumentError, "You must assign a value to #{self.name}.logger"
95
+ end
96
+
97
+ #
98
+ # optional settings
99
+ #
100
+ attr_accessor :escalation_recipients
101
+ attr_accessor :email_environment
102
+ attr_accessor :filter_list_filename
103
+ attr_accessor :mailer_send_enabled
104
+ attr_accessor :eventmachine_safe
105
+ attr_accessor :eventmachine_synchrony
106
+ attr_accessor :custom_data_hook
107
+ attr_accessor :post_log_error_hook
108
+ attr_accessor :stub_handler
109
+
110
+ @filter_list_filename = "./config/exception_filters.yml"
111
+ @mailer_send_enabled = true
112
+ @email_environment = ""
113
+ @eventmachine_safe = false
114
+ @eventmachine_synchrony = false
115
+
116
+ # set this for operation within an eventmachine reactor
117
+ def eventmachine_safe=(bool)
118
+ if bool != true && bool != false
119
+ raise ArgumentError, "#{self.name}.eventmachine_safe must be a boolean."
120
+ end
121
+ if bool
122
+ require 'eventmachine'
123
+ require 'em/protocols/smtpclient'
124
+ end
125
+ @eventmachine_safe = bool
126
+ end
82
127
 
128
+ # set this for EM::Synchrony async operation
129
+ def eventmachine_synchrony=(bool)
130
+ if bool != true && bool != false
131
+ raise ArgumentError, "#{self.name}.eventmachine_synchrony must be a boolean."
132
+ end
133
+ @eventmachine_synchrony = bool
134
+ end
135
+
136
+ #
137
+ # internal settings (don't set directly)
138
+ #
83
139
  attr_accessor :current_controller
84
140
  attr_accessor :last_exception_timestamp
85
141
  attr_accessor :periodic_exception_intervals
86
- attr_accessor :logger
87
142
 
88
143
  #
89
144
  # Gets called by Rack Middleware: DebugExceptions or ShowExceptions
@@ -97,6 +152,10 @@ EOF
97
152
  timestamp = set_log_error_timestamp
98
153
  exception_data = exception_to_data(exception, env, timestamp)
99
154
 
155
+ if stub_handler
156
+ return stub_handler.handle_stub_log_error(exception_data)
157
+ end
158
+
100
159
  # TODO: add a more interesting custom description, like:
101
160
  # custom_description = ": caught and processed by Rack middleware filter #{rack_filter}"
102
161
  # which would be nice, but would also require changing quite a few tests
@@ -106,7 +165,10 @@ EOF
106
165
  if should_send_email?
107
166
  controller = env['action_controller.instance']
108
167
  # controller may not exist in some cases (like most 404 errors)
109
- extract_and_merge_controller_data(controller, exception_data) if controller
168
+ if controller
169
+ extract_and_merge_controller_data(controller, exception_data)
170
+ controller.session["last_exception_timestamp"] = ExceptionHandling.last_exception_timestamp
171
+ end
110
172
  log_error_email(exception_data, exception)
111
173
  end
112
174
  end
@@ -125,6 +187,10 @@ EOF
125
187
  timestamp = set_log_error_timestamp
126
188
  data = exception_to_data(ex, exception_context, timestamp)
127
189
 
190
+ if stub_handler
191
+ return stub_handler.handle_stub_log_error(data)
192
+ end
193
+
128
194
  write_exception_to_log(ex, exception_context, timestamp)
129
195
 
130
196
  if treat_as_local
@@ -151,6 +217,8 @@ EOF
151
217
  log_error_email(data, ex)
152
218
  end
153
219
 
220
+ rescue LogErrorStub::UnexpectedExceptionLogged, LogErrorStub::ExpectedExceptionNotLogged
221
+ raise
154
222
  rescue Exception => ex
155
223
  $stderr.puts("ExceptionHandling.log_error rescued exception while logging #{exception_context}: #{exception_or_string}:\n#{ex.class}: #{ex}\n#{ex.backtrace.join("\n")}")
156
224
  write_exception_to_log(ex, "ExceptionHandling.log_error rescued exception while logging #{exception_context}: #{exception_or_string}", timestamp)
@@ -178,7 +246,7 @@ EOF
178
246
  def extract_and_merge_controller_data(controller, data)
179
247
  data[:request] = {
180
248
  :params => controller.request.parameters.to_hash,
181
- :rails_root => Rails.root,
249
+ :rails_root => defined?(Rails) ? Rails.root : "Rails.root not defined. Is this a test environment?",
182
250
  :url => controller.complete_request_uri
183
251
  }
184
252
  data[:environment].merge!(controller.request.env.to_hash)
@@ -217,12 +285,23 @@ EOF
217
285
  log_error ex, exception_context
218
286
  end
219
287
 
220
- def ensure_escalation( email_subject )
288
+ def escalate_error(exception_or_string, email_subject)
289
+ ex = make_exception(exception_or_string)
290
+ log_error(ex)
291
+ escalate(email_subject, ex, ExceptionHandling.last_exception_timestamp)
292
+ end
293
+
294
+ def escalate_warning(message, email_subject)
295
+ ex = Warning.new(message)
296
+ log_error(ex)
297
+ escalate(email_subject, ex, ExceptionHandling.last_exception_timestamp)
298
+ end
299
+
300
+ def ensure_escalation(email_subject)
221
301
  begin
222
302
  yield
223
303
  rescue => ex
224
- log_error ex
225
- escalate(email_subject, ex, ExceptionHandling.last_exception_timestamp)
304
+ escalate_error(ex, email_subject)
226
305
  nil
227
306
  end
228
307
  end
@@ -232,7 +311,7 @@ EOF
232
311
  end
233
312
 
234
313
  def should_send_email?
235
- defined?( EXCEPTION_HANDLING_MAILER_SEND_MAIL ) && EXCEPTION_HANDLING_MAILER_SEND_MAIL
314
+ ExceptionHandling.mailer_send_enabled
236
315
  end
237
316
 
238
317
  def trace_timing(description)
@@ -253,9 +332,14 @@ EOF
253
332
  end
254
333
  end
255
334
 
256
- # TODO: fix test to not use this.
257
335
  def enhance_exception_data(data)
258
-
336
+ return if ! ExceptionHandling.custom_data_hook
337
+ begin
338
+ ExceptionHandling.custom_data_hook.call(data)
339
+ rescue Exception => ex
340
+ # can't call log_error here or we will blow the call stack
341
+ log_info( "Unable to execute custom custom_data_hook callback. #{ex.message} #{ex.backtrace.each {|l| "#{l}\n"}}" )
342
+ end
259
343
  end
260
344
 
261
345
  private
@@ -281,18 +365,30 @@ EOF
281
365
  Errplane.transmit(exc, :custom_data => data) unless exc.is_a?(Warning)
282
366
  end
283
367
 
368
+ execute_custom_log_error_callback(data, exc)
369
+
284
370
  nil
285
371
  end
286
372
 
373
+ def execute_custom_log_error_callback(exception_data, exception)
374
+ return if ! ExceptionHandling.post_log_error_hook
375
+ begin
376
+ ExceptionHandling.post_log_error_hook.call(exception_data, exception)
377
+ rescue Exception => ex
378
+ # can't call log_error here or we will blow the call stack
379
+ log_info( "Unable to execute custom log_error callback. #{ex.message} #{ex.backtrace.each {|l| "#{l}\n"}}" )
380
+ end
381
+ end
382
+
287
383
  def escalate( email_subject, ex, timestamp )
288
384
  data = exception_to_data( ex, nil, timestamp )
289
385
  deliver(ExceptionHandling::Mailer.escalation_notification(email_subject, data))
290
386
  end
291
387
 
292
388
  def deliver(mail_object)
293
- if defined?(EVENTMACHINE_EXCEPTION_HANDLING) && EVENTMACHINE_EXCEPTION_HANDLING
389
+ if ExceptionHandling.eventmachine_safe
294
390
  EventMachine.schedule do # in case we're running outside the reactor
295
- async_send_method = EVENTMACHINE_EXCEPTION_HANDLING == :Synchrony ? :asend : :send
391
+ async_send_method = ExceptionHandling.eventmachine_synchrony ? :asend : :send
296
392
  smtp_settings = ActionMailer::Base.smtp_settings
297
393
  send_deferrable = EventMachine::Protocols::SmtpClient.__send__(
298
394
  async_send_method,
@@ -303,7 +399,7 @@ EOF
303
399
  :auth => {:type=>:plain, :username=>smtp_settings[:user_name], :password=>smtp_settings[:password]},
304
400
  :from => mail_object['from'].to_s,
305
401
  :to => mail_object['to'].to_s,
306
- :body => mail_object.body.to_s
402
+ :content => "#{mail_object}.\r\n"
307
403
  }
308
404
  )
309
405
  send_deferrable.errback { |err| ExceptionHandling.logger.fatal("Failed to email by SMTP: #{err.inspect}") }
@@ -320,7 +416,7 @@ EOF
320
416
  yield
321
417
  end
322
418
  rescue StandardError, MailerTimeout => ex
323
- $stderr.puts("ExceptionHandling::safe_email_deliver rescued: #{ex.class}: #{ex}\n#{ex.backtrace.join("\n")}")
419
+ #$stderr.puts("ExceptionHandling::safe_email_deliver rescued: #{ex.class}: #{ex}\n#{ex.backtrace.join("\n")}")
324
420
  log_error( ex, "ExceptionHandling::safe_email_deliver", nil, true )
325
421
  end
326
422
 
@@ -341,10 +437,10 @@ EOF
341
437
  def normalize_exception_data( data )
342
438
  if data[:location].nil?
343
439
  data[:location] = {}
344
- if data[:request] && data[:request].key?( :params )
345
- data[:location][:controller] = data[:request][:params]['controller']
346
- data[:location][:action] = "fake action"
347
- end
440
+ if data[:request] && data[:request].key?( :params )
441
+ data[:location][:controller] = data[:request][:params]['controller']
442
+ data[:location][:action] = data[:request][:params]['action']
443
+ end
348
444
  end
349
445
  if data[:backtrace] && data[:backtrace].first
350
446
  first_line = data[:backtrace].first
@@ -375,7 +471,7 @@ EOF
375
471
  end
376
472
 
377
473
  def exception_filters
378
- @exception_filters ||= ExceptionFilters.new( EXCEPTION_FILTER_LIST_PATH )
474
+ @exception_filters ||= ExceptionFilters.new( ExceptionHandling.filter_list_filename )
379
475
  end
380
476
 
381
477
  def clean_backtrace(exception)
@@ -464,7 +560,7 @@ EOF
464
560
  data[:session] = exception_context['rack.session']
465
561
  data[:environment] = exception_context
466
562
  else
467
- data[:error] = "#{data[:error_string]}#{': ' + exception_context unless exception_context.blank?}"
563
+ data[:error] = "#{data[:error_string]}#{': ' + exception_context.to_s unless exception_context.blank?}"
468
564
  data[:environment] = { :message => exception_context }
469
565
  end
470
566
  data
@@ -573,66 +669,6 @@ EOF
573
669
  def last_modified_time
574
670
  File.mtime( @filter_path )
575
671
  end
576
- end
577
-
578
-
579
- public
580
-
581
- module Methods # included on models and controllers
582
- protected
583
- def log_error(exception_or_string, exception_context = '')
584
- controller = self if respond_to?(:request) && respond_to?(:session)
585
- ExceptionHandling.log_error(exception_or_string, exception_context, controller)
586
- end
587
672
 
588
- def log_error_rack(exception_or_string, exception_context = '', rack_filter = '')
589
- ExceptionHandling.log_error_rack(exception_or_string, exception_context, controller)
590
- end
591
-
592
- def log_warning(message)
593
- log_error(Warning.new(message))
594
- end
595
-
596
- def log_info(message)
597
- ExceptionHandling.logger.info( message )
598
- end
599
-
600
- def log_debug(message)
601
- ExceptionHandling.logger.debug( message )
602
- end
603
-
604
- def ensure_safe(exception_context = "")
605
- begin
606
- yield
607
- rescue => ex
608
- log_error ex, exception_context
609
- nil
610
- end
611
- end
612
-
613
- def ensure_escalation(*args)
614
- ExceptionHandling.ensure_escalation(*args) do
615
- yield
616
- end
617
- end
618
-
619
- # Store aside the current controller when included
620
- LONG_REQUEST_SECONDS = (defined?(Rails) && Rails.env == 'test' ? 300 : 30)
621
- def set_current_controller
622
- ExceptionHandling.current_controller = self
623
- result = nil
624
- time = Benchmark.measure do
625
- result = yield
626
- end
627
- name = " in #{controller_name}::#{action_name}" rescue " "
628
- log_error( "Long controller action detected#{name} %.4fs " % time.real ) if time.real > LONG_REQUEST_SECONDS && !['development', 'test'].include?(Rails.env)
629
- result
630
- ensure
631
- ExceptionHandling.current_controller = nil
632
- end
633
-
634
- def self.included( controller )
635
- controller.around_filter :set_current_controller if controller.respond_to? :around_filter
636
- end
637
673
  end
638
674
  end
data/test/test_helper.rb CHANGED
@@ -2,9 +2,44 @@ require 'active_support'
2
2
  require 'active_support/time'
3
3
  require 'active_support/test_case'
4
4
  require 'action_mailer'
5
+ require 'hobo_support'
5
6
  require 'shoulda'
6
- require 'mocha/setup'
7
- require './test/mocha_patch'
7
+ require 'rr'
8
+ require 'minitest/autorun'
9
+ require 'pry'
10
+
11
+ require 'exception_handling'
12
+ require 'exception_handling/testing'
13
+
14
+ class LoggerStub
15
+ attr_accessor :logged
16
+
17
+ def initialize
18
+ clear
19
+ end
20
+
21
+ def info(message)
22
+ logged << message
23
+ end
24
+
25
+ def warn(message)
26
+ logged << message
27
+ end
28
+
29
+ def fatal(message)
30
+ logged << message
31
+ end
32
+
33
+ def clear
34
+ @logged = []
35
+ end
36
+ end
37
+
38
+ ExceptionHandling.logger = LoggerStub.new
39
+
40
+ def dont_stub_log_error
41
+ true
42
+ end
8
43
 
9
44
  ActionMailer::Base.delivery_method = :test
10
45
 
@@ -22,6 +57,16 @@ class ActiveSupport::TestCase
22
57
  Time.now_override = nil
23
58
 
24
59
  ActionMailer::Base.deliveries.clear
60
+
61
+ ExceptionHandling.email_environment = 'Test'
62
+ ExceptionHandling.sender_address = 'server@example.com'
63
+ ExceptionHandling.exception_recipients = 'exceptions@example.com'
64
+ ExceptionHandling.escalation_recipients = 'escalation@example.com'
65
+ ExceptionHandling.server_name = 'server'
66
+ ExceptionHandling.mailer_send_enabled = true
67
+ ExceptionHandling.filter_list_filename = "./config/exception_filters.yml"
68
+ ExceptionHandling.eventmachine_safe = false
69
+ ExceptionHandling.eventmachine_synchrony = false
25
70
  end
26
71
 
27
72
  teardown do
@@ -68,6 +113,14 @@ class ActiveSupport::TestCase
68
113
  end
69
114
  end
70
115
 
116
+ def assert_equal_with_diff arg1, arg2, msg = ''
117
+ if arg1 == arg2
118
+ assert true # To keep the assertion count accurate
119
+ else
120
+ assert_equal arg1, arg2, "#{msg}\n#{Diff.compare(arg1, arg2)}"
121
+ end
122
+ end
123
+
71
124
  class Time
72
125
  class << self
73
126
  attr_reader :now_override
@@ -0,0 +1,83 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+
3
+ module ExceptionHandling
4
+ class LogErrorStubTest < ActiveSupport::TestCase
5
+
6
+ include LogErrorStub
7
+
8
+ context "while running tests" do
9
+ setup do
10
+ setup_log_error_stub
11
+ end
12
+
13
+ teardown do
14
+ teardown_log_error_stub
15
+ end
16
+
17
+ should "raise an error when log_error and log_warning are called" do
18
+ begin
19
+ ExceptionHandling.log_error("Something happened")
20
+ flunk
21
+ rescue Exception => ex #LogErrorStub::UnexpectedExceptionLogged => ex
22
+ assert ex.to_s.starts_with?("StandardError: Something happened"), ex.to_s
23
+ end
24
+
25
+ begin
26
+ class ::RaisedError < StandardError; end
27
+ raise ::RaisedError, "This should raise"
28
+ rescue => ex
29
+ begin
30
+ ExceptionHandling.log_error(ex)
31
+ rescue LogErrorStub::UnexpectedExceptionLogged => ex_inner
32
+ assert ex_inner.to_s.starts_with?("RaisedError: This should raise"), ex_inner.to_s
33
+ end
34
+ end
35
+ end
36
+
37
+ should "allow for the regex specification of an expected exception to be ignored" do
38
+ exception_pattern = /StandardError: This is a test error/
39
+ assert_nil exception_whitelist # test that exception expectations are cleared
40
+ expects_exception(exception_pattern)
41
+ assert_equal exception_pattern, exception_whitelist[0][0]
42
+ begin
43
+ ExceptionHandling.log_error("This is a test error")
44
+ rescue => ex
45
+ flunk # Shouldn't raise an error in this case
46
+ end
47
+ end
48
+
49
+ should "allow for the string specification of an expected exception to be ignored" do
50
+ exception_pattern = "StandardError: This is a test error"
51
+ assert_nil exception_whitelist # test that exception expectations are cleared
52
+ expects_exception(exception_pattern)
53
+ assert_equal exception_pattern, exception_whitelist[0][0]
54
+ begin
55
+ ExceptionHandling.log_error("This is a test error")
56
+ rescue => ex
57
+ flunk # Shouldn't raise an error in this case
58
+ end
59
+ end
60
+
61
+ should "allow multiple errors to be ignored" do
62
+ class IgnoredError < StandardError; end
63
+ assert_nil exception_whitelist # test that exception expectations are cleared
64
+ expects_exception(/StandardError: This is a test error/)
65
+ expects_exception(/IgnoredError: This should be ignored/)
66
+ ExceptionHandling.log_error("This is a test error")
67
+ begin
68
+ raise IgnoredError, "This should be ignored"
69
+ rescue IgnoredError => ex
70
+ ExceptionHandling.log_error(ex)
71
+ end
72
+ end
73
+
74
+ should "expect exception twice if declared twice" do
75
+ expects_exception(/StandardError: ERROR: I love lamp/)
76
+ expects_exception(/StandardError: ERROR: I love lamp/)
77
+ ExceptionHandling.log_error("ERROR: I love lamp")
78
+ ExceptionHandling.log_error("ERROR: I love lamp")
79
+ end
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,79 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+
3
+ module ExceptionHandling
4
+ class MailerTest < ActionMailer::TestCase
5
+
6
+ include ActionDispatch::Assertions::SelectorAssertions
7
+ tests ExceptionHandling::Mailer
8
+
9
+ def dont_stub_log_error
10
+ true
11
+ end
12
+
13
+ context "ExceptionHandling::Mailer" do
14
+ setup do
15
+ ExceptionHandling.email_environment = 'Test'
16
+ ExceptionHandling.sender_address = %("Test Exception Mailer" <null_exception@invoca.com>)
17
+ ExceptionHandling.exception_recipients = ['test_exception@invoca.com']
18
+ ExceptionHandling.escalation_recipients = ['test_escalation@invoca.com']
19
+ end
20
+
21
+ should "deliver" do
22
+ #ActionMailer::Base.delivery_method = :smtp
23
+ result = ExceptionHandling::Mailer.exception_notification({ :error => "Test Error."}).deliver
24
+ assert_match /Test Error./, result.body.to_s
25
+ assert_equal_with_diff ['test_exception@invoca.com'], result.to
26
+ assert_emails 1
27
+ end
28
+
29
+ context "log_parser_exception_notification" do
30
+ should "send with string" do
31
+ result = ExceptionHandling::Mailer.log_parser_exception_notification("This is my fake error", "My Fake Subj").deliver
32
+ assert_equal "Test exception: My Fake Subj: This is my fake error", result.subject
33
+ assert_match(/This is my fake error/, result.body.to_s)
34
+ assert_emails 1
35
+ end
36
+ end
37
+
38
+ context "escalation_notification" do
39
+ should "send all the information" do
40
+ ExceptionHandling.email_environment = 'Staging Full'
41
+ ExceptionHandling.server_name = 'test-fe3'
42
+
43
+ ExceptionHandling::Mailer.escalation_notification("Your Favorite <b>Feature<b> Failed", :error_string => "It failed because of an error\n <i>More Info<i>", :timestamp => 1234567 ).deliver
44
+
45
+ assert_emails 1
46
+ result = ActionMailer::Base.deliveries.last
47
+ body_html = HTML::Document.new(result.body.to_s)
48
+ assert_equal_with_diff ['test_escalation@invoca.com'], result.to
49
+ assert_equal ["Test Escalation Mailer <null_escalation@invoca.com>"], result[:from].formatted
50
+ assert_equal "Staging Full Escalation: Your Favorite <b>Feature<b> Failed", result.subject
51
+ assert_select body_html.root, "html" do
52
+ assert_select "title", "Exception Escalation"
53
+ assert_select "body br", { :count => 4 }, result.body.to_s # plus 1 for the multiline summary
54
+ assert_select "body h3", "Your Favorite &lt;b&gt;Feature&lt;b&gt; Failed", result.body.to_s
55
+ assert_select "body", /1234567/
56
+ assert_select "body", /It failed because of an error\n &lt;i&gt;More Info&lt;i&gt;/
57
+ assert_select "body", /test-fe3/
58
+ #assert_select "body", /#{Web::Application::GIT_REVISION}/
59
+ end
60
+ end
61
+
62
+ should "use defaults for missing fields" do
63
+ result = ExceptionHandling::Mailer.escalation_notification("Your Favorite Feature Failed", :error_string => "It failed because of an error\n More Info")
64
+ body_html = HTML::Document.new(result.body.to_s)
65
+
66
+ assert_equal_with_diff ['test_escalation@invoca.com'], result.to
67
+ assert_equal ["null_escalation@invoca.com"], result.from
68
+ assert_equal 'Test Escalation: Your Favorite Feature Failed', result.subject
69
+ assert_select body_html.root, "html" do
70
+ assert_select "body i", true, result.body.to_s do |is|
71
+ assert_select is[0], "i", 'no error #'
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,59 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+
3
+ require "exception_handling/testing"
4
+
5
+ module ExceptionHandling
6
+ class MethodsTest < ActiveSupport::TestCase
7
+
8
+ def dont_stub_log_error
9
+ true
10
+ end
11
+
12
+ context "ExceptionHandling.Methods" do
13
+ setup do
14
+ @controller = Testing::ControllerStub.new
15
+ ExceptionHandling.stub_handler = nil
16
+ end
17
+
18
+ teardown do
19
+ Time.now_override = nil
20
+ end
21
+
22
+ should "set the around filter" do
23
+ assert_equal :set_current_controller, Testing::ControllerStub.around_filter_method
24
+ assert_nil ExceptionHandling.current_controller
25
+ @controller.simulate_around_filter( ) do
26
+ assert_equal @controller, ExceptionHandling.current_controller
27
+ end
28
+ assert_nil ExceptionHandling.current_controller
29
+ end
30
+
31
+ should "use the current_controller when available" do
32
+ mock(ExceptionHandling.logger).fatal(/blah/)
33
+ @controller.simulate_around_filter do
34
+ ExceptionHandling.log_error( ArgumentError.new("blah") )
35
+ mail = ActionMailer::Base.deliveries.last
36
+ assert_match( @controller.request.request_uri, mail.body.to_s )
37
+ # assert_match( Username.first.username.to_s, mail.body.to_s ) if defined?(Username)
38
+ end
39
+ end
40
+
41
+ should "report long running controller action" do
42
+ # This was the original stub:
43
+ # Rails.expects(:env).times(2).returns('production')
44
+ # but this stubbing approach no longer works
45
+ # Rails is setting the long controller timeout on module load
46
+ # in exception_handling.rb - that happens before this test ever gets run
47
+ # we can set Rails.env here, which we do, because it affects part of the real-time
48
+ # logic check for whether to raise (which we want). To overcome the setting
49
+ # on the long controller timeout I have set the Time.now_override to 1.day.from_now
50
+ # instead of 1.hour.from_now
51
+ mock(ExceptionHandling).log_error(/Long controller action detected in #{@controller.class.name.split("::").last}::test_action/, anything, anything)
52
+ @controller.simulate_around_filter( ) do
53
+ Time.now_override = 1.day.from_now
54
+ end
55
+ end
56
+ end
57
+
58
+ end
59
+ end