exception_handling 1.2.1 → 2.2.1

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