exception_handling 2.6.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.jenkins/Jenkinsfile +24 -8
  4. data/.rspec +3 -0
  5. data/CHANGELOG.md +18 -1
  6. data/Gemfile +4 -4
  7. data/Gemfile.lock +66 -57
  8. data/Rakefile +7 -6
  9. data/gemfiles/rails_4.gemfile +4 -4
  10. data/gemfiles/rails_5.gemfile +4 -4
  11. data/gemfiles/rails_6.gemfile +4 -4
  12. data/lib/exception_handling.rb +5 -2
  13. data/lib/exception_handling/exception_info.rb +3 -6
  14. data/lib/exception_handling/log_stub_error.rb +2 -1
  15. data/lib/exception_handling/logging_methods.rb +27 -0
  16. data/lib/exception_handling/methods.rb +6 -53
  17. data/lib/exception_handling/testing.rb +20 -10
  18. data/lib/exception_handling/version.rb +1 -1
  19. data/{test → spec}/helpers/controller_helpers.rb +0 -0
  20. data/{test → spec}/helpers/exception_helpers.rb +2 -2
  21. data/{test → spec}/rake_test_warning_false.rb +0 -0
  22. data/{test/test_helper.rb → spec/spec_helper.rb} +50 -39
  23. data/spec/unit/exception_handling/exception_catalog_spec.rb +85 -0
  24. data/spec/unit/exception_handling/exception_description_spec.rb +82 -0
  25. data/{test/unit/exception_handling/exception_info_test.rb → spec/unit/exception_handling/exception_info_spec.rb} +118 -99
  26. data/{test/unit/exception_handling/honeybadger_callbacks_test.rb → spec/unit/exception_handling/honeybadger_callbacks_spec.rb} +20 -20
  27. data/{test/unit/exception_handling/log_error_stub_test.rb → spec/unit/exception_handling/log_error_stub_spec.rb} +38 -22
  28. data/spec/unit/exception_handling/logging_methods_spec.rb +38 -0
  29. data/{test/unit/exception_handling/mailer_test.rb → spec/unit/exception_handling/mailer_spec.rb} +17 -17
  30. data/spec/unit/exception_handling/methods_spec.rb +105 -0
  31. data/spec/unit/exception_handling/sensu_spec.rb +51 -0
  32. data/{test/unit/exception_handling_test.rb → spec/unit/exception_handling_spec.rb} +348 -329
  33. metadata +32 -28
  34. data/test/unit/exception_handling/exception_catalog_test.rb +0 -85
  35. data/test/unit/exception_handling/exception_description_test.rb +0 -82
  36. data/test/unit/exception_handling/methods_test.rb +0 -84
  37. data/test/unit/exception_handling/sensu_test.rb +0 -52
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #
4
- # Used by functional tests to track exceptions.
4
+ # Test Helper that supports Minitest::Test and Test::Unit
5
+ # Used by tests in the consumers of this gem to track exceptions.
5
6
  #
6
7
 
7
8
  module LogErrorStub
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require 'active_support/core_ext/module/delegation.rb'
5
+
6
+ module ExceptionHandling
7
+ module LoggingMethods # included on models and controllers
8
+ extend ActiveSupport::Concern
9
+
10
+ protected
11
+
12
+ delegate :log_error_rack, :log_warning, :log_info, :log_debug, :escalate_error, :escalate_warning, :ensure_escalation, :alert_warning, :log_error, to: ExceptionHandling
13
+
14
+ def ensure_safe(exception_context = "")
15
+ yield
16
+ rescue => ex
17
+ log_error ex, exception_context
18
+ nil
19
+ end
20
+
21
+ def ensure_alert(*args)
22
+ ExceptionHandling.ensure_alert(*args) do
23
+ yield
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,65 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/concern'
4
+ require_relative 'logging_methods'
4
5
 
5
6
  module ExceptionHandling
6
7
  module Methods # included on models and controllers
7
8
  extend ActiveSupport::Concern
9
+ include ExceptionHandling::LoggingMethods
8
10
 
9
11
  protected
10
12
 
11
- def log_error(exception_or_string, exception_context = '')
12
- controller = self if respond_to?(:request) && respond_to?(:session)
13
- ExceptionHandling.log_error(exception_or_string, exception_context, controller)
14
- end
15
-
16
- def log_error_rack(exception_or_string, exception_context = '', rack_filter = '')
17
- ExceptionHandling.log_error_rack(exception_or_string, exception_context, rack_filter)
18
- end
19
-
20
- def log_warning(message)
21
- ExceptionHandling.log_warning(message)
22
- end
23
-
24
- def log_info(message)
25
- ExceptionHandling.logger.info(message)
26
- end
27
-
28
- def log_debug(message)
29
- ExceptionHandling.logger.debug(message)
30
- end
31
-
32
- def ensure_safe(exception_context = "")
33
- yield
34
- rescue => ex
35
- log_error ex, exception_context
36
- nil
37
- end
38
-
39
- def escalate_error(exception_or_string, email_subject)
40
- ExceptionHandling.escalate_error(exception_or_string, email_subject)
41
- end
42
-
43
- def escalate_warning(message, email_subject)
44
- ExceptionHandling.escalate_warning(message, email_subject)
45
- end
46
-
47
- def ensure_escalation(*args)
48
- ExceptionHandling.ensure_escalation(*args) do
49
- yield
50
- end
51
- end
52
-
53
- def alert_warning(*args)
54
- ExceptionHandling.alert_warning(*args)
55
- end
56
-
57
- def ensure_alert(*args)
58
- ExceptionHandling.ensure_alert(*args) do
59
- yield
60
- end
61
- end
62
-
63
13
  def long_controller_action_timeout
64
14
  if defined?(Rails) && Rails.respond_to?(:env) && Rails.env == 'test'
65
15
  300
@@ -88,7 +38,10 @@ module ExceptionHandling
88
38
  end
89
39
 
90
40
  included do
91
- around_filter :set_current_controller if respond_to? :around_filter
41
+ Deprecation3_0.deprecation_warning('ExceptionHandling::Methods', 'include LoggingMethods; in controllers, set your own around_filter to set logging context')
42
+ if respond_to? :around_filter
43
+ around_filter :set_current_controller
44
+ end
92
45
  end
93
46
 
94
47
  class_methods do
@@ -4,7 +4,7 @@
4
4
 
5
5
  module ExceptionHandling
6
6
  module Testing
7
- class ControllerStub
7
+ class ControllerStubBase
8
8
 
9
9
  class Request
10
10
  attr_accessor :parameters, :protocol, :host, :request_uri, :env, :session_options
@@ -25,7 +25,7 @@ module ExceptionHandling
25
25
  attr_accessor :around_filter_method
26
26
 
27
27
  def around_filter(method)
28
- ControllerStub.around_filter_method = method
28
+ self.around_filter_method = method
29
29
  end
30
30
  end
31
31
 
@@ -44,14 +44,6 @@ module ExceptionHandling
44
44
  end
45
45
  end
46
46
 
47
- def simulate_around_filter(&block)
48
- set_current_controller(&block)
49
- end
50
-
51
- def controller_name
52
- "ControllerStub"
53
- end
54
-
55
47
  def action_name
56
48
  "test_action"
57
49
  end
@@ -59,9 +51,27 @@ module ExceptionHandling
59
51
  def complete_request_uri
60
52
  "#{@request.protocol}#{@request.host}#{@request.request_uri}"
61
53
  end
54
+ end
55
+
56
+ class LoggingMethodsControllerStub < ControllerStubBase
57
+ include ExceptionHandling::LoggingMethods
58
+
59
+ def controller_name
60
+ "LoggingMethodsControllerStub"
61
+ end
62
+ end
62
63
 
64
+ class MethodsControllerStub < ControllerStubBase
63
65
  include ExceptionHandling::Methods
64
66
  set_long_controller_action_timeout 2
67
+
68
+ def simulate_around_filter(&block)
69
+ set_current_controller(&block)
70
+ end
71
+
72
+ def controller_name
73
+ "MethodsControllerStub"
74
+ end
65
75
  end
66
76
  end
67
77
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ExceptionHandling
4
- VERSION = '2.6.0'
4
+ VERSION = '2.8.0'
5
5
  end
@@ -7,7 +7,7 @@ module ExceptionHelpers
7
7
 
8
8
  def exception_with_nil_message
9
9
  exception_with_nil_message = RuntimeError.new(nil)
10
- stub(exception_with_nil_message).message { nil }
10
+ allow(exception_with_nil_message).to receive(:message).and_return(nil)
11
11
  exception_with_nil_message
12
12
  end
13
13
 
@@ -15,6 +15,6 @@ module ExceptionHelpers
15
15
 
16
16
  def capture_notifications
17
17
  @sent_notifications = []
18
- stub(ExceptionHandling).send_exception_to_honeybadger(anything) { |exception_info| @sent_notifications << exception_info }
18
+ allow(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(anything) { |exception_info| @sent_notifications << exception_info }
19
19
  end
20
20
  end
@@ -1,17 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support'
4
- require 'active_support/time'
5
- require 'active_support/test_case'
6
- require 'action_mailer'
7
- require 'action_dispatch'
8
- require 'shoulda'
9
- require 'rr'
10
- require 'minitest/autorun'
11
- require "minitest/reporters"
12
-
13
- junit_ouptut_dir = ENV["JUNIT_OUTPUT_DIR"].presence || "test/reports"
14
- Minitest::Reporters.use!([Minitest::Reporters::DefaultReporter.new, Minitest::Reporters::JUnitReporter.new(junit_ouptut_dir)])
3
+ require 'rspec'
4
+ require 'rspec/mocks'
5
+ require 'rspec_junit_formatter'
15
6
 
16
7
  require 'pry'
17
8
  require 'honeybadger'
@@ -20,8 +11,6 @@ require 'contextual_logger'
20
11
  require 'exception_handling'
21
12
  require 'exception_handling/testing'
22
13
 
23
- ActiveSupport::TestCase.test_order = :sorted
24
-
25
14
  class LoggerStub
26
15
  include ContextualLogger::LoggerMixin
27
16
  attr_accessor :logged, :level
@@ -87,27 +76,17 @@ end
87
76
 
88
77
  ActionMailer::Base.delivery_method = :test
89
78
 
90
- _ = ActiveSupport
91
- _ = ActiveSupport::TestCase
92
79
 
93
- class ActiveSupport::TestCase
94
- @@constant_overrides = []
80
+ module TestHelper
81
+ @constant_overrides = []
82
+ class << self
83
+ attr_accessor :constant_overrides
84
+ end
95
85
 
96
- setup do
97
- unless @@constant_overrides.nil? || @@constant_overrides.empty?
98
- raise "Uh-oh! constant_overrides left over: #{@@constant_overrides.inspect}"
99
- end
100
86
 
101
- unless defined?(Rails) && defined?(Rails.env)
102
- module ::Rails
103
- class << self
104
- attr_writer :env
105
-
106
- def env
107
- @env ||= 'test'
108
- end
109
- end
110
- end
87
+ def setup_constant_overrides
88
+ unless TestHelper.constant_overrides.nil? || TestHelper.constant_overrides.empty?
89
+ raise "Uh-oh! constant_overrides left over: #{TestHelper.constant_overrides.inspect}"
111
90
  end
112
91
 
113
92
  Time.now_override = nil
@@ -127,8 +106,8 @@ class ActiveSupport::TestCase
127
106
  ExceptionHandling.sensu_prefix = ""
128
107
  end
129
108
 
130
- teardown do
131
- @@constant_overrides&.reverse&.each do |parent_module, k, v|
109
+ def teardown_constant_overrides
110
+ TestHelper.constant_overrides&.reverse&.each do |parent_module, k, v|
132
111
  ExceptionHandling.ensure_safe "constant cleanup #{k.inspect}, #{parent_module}(#{parent_module.class})::#{v.inspect}(#{v.class})" do
133
112
  silence_warnings do
134
113
  if v == :never_defined
@@ -139,7 +118,7 @@ class ActiveSupport::TestCase
139
118
  end
140
119
  end
141
120
  end
142
- @@constant_overrides = []
121
+ TestHelper.constant_overrides = []
143
122
  end
144
123
 
145
124
  def set_test_const(const_name, value)
@@ -159,7 +138,7 @@ class ActiveSupport::TestCase
159
138
  end
160
139
  end
161
140
 
162
- @@constant_overrides << [final_parent_module, final_const_name, original_value]
141
+ TestHelper.constant_overrides << [final_parent_module, final_const_name, original_value]
163
142
 
164
143
  silence_warnings { final_parent_module.const_set(final_const_name, value) }
165
144
  end
@@ -171,15 +150,15 @@ class ActiveSupport::TestCase
171
150
  else
172
151
  original_count = 0
173
152
  end
174
- assert_equal expected, ActionMailer::Base.deliveries.size - original_count, "wrong number of emails#{': ' + message.to_s if message}"
153
+ expect(ActionMailer::Base.deliveries.size - original_count).to eq(expected), "wrong number of emails#{': ' + message.to_s if message}"
175
154
  end
176
155
  end
177
156
 
178
157
  def assert_equal_with_diff(arg1, arg2, msg = '')
179
158
  if arg1 == arg2
180
- assert true # To keep the assertion count accurate
159
+ expect(true).to be_truthy # To keep the assertion count accurate
181
160
  else
182
- assert_equal arg1, arg2, "#{msg}\n#{Diff.compare(arg1, arg2)}"
161
+ expect(arg1).to eq(arg2), "#{msg}\n#{Diff.compare(arg1, arg2)}"
183
162
  end
184
163
  end
185
164
 
@@ -210,3 +189,35 @@ class Time
210
189
  end
211
190
  end
212
191
  end
192
+
193
+ RSpec.configure do |config|
194
+ config.add_formatter(RspecJunitFormatter, 'spec/reports/rspec.xml')
195
+ config.include TestHelper
196
+
197
+ config.before(:each) do
198
+ setup_constant_overrides
199
+ unless defined?(Rails) && defined?(Rails.env)
200
+ module Rails
201
+ class << self
202
+ attr_writer :env
203
+
204
+ def env
205
+ @env ||= 'test'
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
211
+
212
+ config.after(:each) do
213
+ teardown_constant_overrides
214
+ end
215
+
216
+ config.mock_with :rspec do |mocks|
217
+ mocks.verify_partial_doubles = true
218
+ end
219
+
220
+ config.expect_with(:rspec, :test_unit)
221
+
222
+ RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = 2_000
223
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('../../spec_helper', __dir__)
4
+
5
+ module ExceptionHandling
6
+ describe ExceptionCatalog do
7
+
8
+ context "With stubbed yaml content" do
9
+ before do
10
+ filter_list = { exception1: { error: "my error message" },
11
+ exception2: { error: "some other message", session: "misc data" } }
12
+ allow(YAML).to receive(:load_file) { filter_list }
13
+
14
+ # bump modified time up to get the above filter loaded
15
+ allow(File).to receive(:mtime) { incrementing_mtime }
16
+ end
17
+
18
+ context "with loaded data" do
19
+ before do
20
+ allow(File).to receive(:mtime) { incrementing_mtime }
21
+ @exception_catalog = ExceptionCatalog.new(ExceptionHandling.filter_list_filename)
22
+ @exception_catalog.send :load_file
23
+ end
24
+
25
+ it "have loaded filters" do
26
+ expect(@exception_catalog.instance_eval("@filters").size).to eq(2)
27
+ end
28
+
29
+ it "find messages in the catalog" do
30
+ expect(!@exception_catalog.find(error: "Scott says unlikely to ever match")).to be_truthy
31
+ end
32
+
33
+ it "find matching data" do
34
+ exception_description = @exception_catalog.find(error: "this is my error message, which should match something")
35
+ expect(exception_description).to be_truthy
36
+ expect(exception_description.filter_name).to eq(:exception1)
37
+ end
38
+ end
39
+
40
+ it "write errors loading the yaml file directly to the log file" do
41
+ @exception_catalog = ExceptionCatalog.new(ExceptionHandling.filter_list_filename)
42
+
43
+ expect(ExceptionHandling).to receive(:log_error).never
44
+ expect(ExceptionHandling).to receive(:write_exception_to_log).with(anything, "ExceptionCatalog#refresh_filters: ./config/exception_filters.yml", anything)
45
+ expect(@exception_catalog).to receive(:load_file) { raise "noooooo" }
46
+
47
+ @exception_catalog.find({})
48
+ end
49
+ end
50
+
51
+ context "with live yaml content" do
52
+ before do
53
+ @filename = File.expand_path('../../../config/exception_filters.yml', __dir__)
54
+ @exception_catalog = ExceptionCatalog.new(@filename)
55
+ expect do
56
+ @exception_catalog.send :load_file
57
+ end.not_to raise_error
58
+ end
59
+
60
+ it "load the filter data" do
61
+ expect(!@exception_catalog.find(error: "Scott says unlikely to ever match")).to be_truthy
62
+ expect(!@exception_catalog.find(error: "Scott says unlikely to ever match")).to be_truthy
63
+ end
64
+ end
65
+
66
+ context "with no yaml content" do
67
+ before do
68
+ @exception_catalog = ExceptionCatalog.new(nil)
69
+ end
70
+
71
+ it "not load filter data" do
72
+ expect(ExceptionHandling).to receive(:write_exception_to_log).with(any_args).never
73
+ @exception_catalog.find(error: "Scott says unlikely to ever match")
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def incrementing_mtime
80
+ @mtime ||= Time.now
81
+ @mtime += 1.day
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require File.expand_path('../../spec_helper', __dir__)
4
+
5
+ module ExceptionHandling
6
+ describe ExceptionDescription do
7
+
8
+ context "Filter" do
9
+ it "allow direct matching of strings" do
10
+ @f = ExceptionDescription.new(:filter1, error: "my error message")
11
+ expect(@f.match?('error' => "my error message")).to be_truthy
12
+ end
13
+
14
+ it "allow direct matching of strings on with symbol keys" do
15
+ @f = ExceptionDescription.new(:filter1, error: "my error message")
16
+ expect(@f.match?(error: "my error message")).to be_truthy
17
+ end
18
+
19
+ it "allow wildcards to cross line boundries" do
20
+ @f = ExceptionDescription.new(:filter1, error: "my error message.*with multiple lines")
21
+ expect(@f.match?(error: "my error message\nwith more than one, with multiple lines")).to be_truthy
22
+ end
23
+
24
+ it "complain when no regexps have a value" do
25
+ expect { ExceptionDescription.new(:filter1, error: nil) }.to raise_exception(ArgumentError, /has all blank regexes/)
26
+ end
27
+
28
+ it "report when an invalid key is passed" do
29
+ expect { ExceptionDescription.new(:filter1, error: "my error message", not_a_parameter: false) }.to raise_exception(ArgumentError, "Unknown section: not_a_parameter")
30
+ end
31
+
32
+ it "allow send_to_honeybadger to be specified and have it disabled by default" do
33
+ expect(!ExceptionDescription.new(:filter1, error: "my error message", send_to_honeybadger: false).send_to_honeybadger).to be_truthy
34
+ expect(ExceptionDescription.new(:filter1, error: "my error message", send_to_honeybadger: true).send_to_honeybadger).to be_truthy
35
+ expect(!ExceptionDescription.new(:filter1, error: "my error message").send_to_honeybadger).to be_truthy
36
+ end
37
+
38
+ it "allow send_metric to be configured" do
39
+ expect(!ExceptionDescription.new(:filter1, error: "my error message", send_metric: false).send_metric).to be_truthy
40
+ expect(ExceptionDescription.new(:filter1, error: "my error message").send_metric).to be_truthy
41
+ end
42
+
43
+ it "provide metric name" do
44
+ expect(ExceptionDescription.new(:filter1, error: "my error message").metric_name).to eq("filter1")
45
+ expect(ExceptionDescription.new(:filter1, error: "my error message", metric_name: :some_other_metric_name).metric_name).to eq("some_other_metric_name")
46
+ end
47
+
48
+ it "replace spaces in metric name" do
49
+ @f = ExceptionDescription.new(:"filter has spaces", error: "my error message")
50
+ expect(@f.metric_name).to eq( "filter_has_spaces")
51
+ end
52
+
53
+ it "allow notes to be recorded" do
54
+ expect(ExceptionDescription.new(:filter1, error: "my error message").notes).to be_nil
55
+ expect(ExceptionDescription.new(:filter1, error: "my error message", notes: "a long string").notes).to eq("a long string")
56
+ end
57
+
58
+ it "not consider config options in the filter set" do
59
+ expect(ExceptionDescription.new(:filter1, error: "my error message", send_metric: false).match?(error: "my error message")).to be_truthy
60
+ expect(ExceptionDescription.new(:filter1, error: "my error message", metric_name: "false").match?(error: "my error message")).to be_truthy
61
+ expect(ExceptionDescription.new(:filter1, error: "my error message", notes: "hey").match?(error: "my error message")).to be_truthy
62
+ end
63
+
64
+ it "provide exception details" do
65
+ exception_description = ExceptionDescription.new(:filter1, error: "my error message", notes: "hey")
66
+
67
+ expected = { "send_metric" => true, "metric_name" => "filter1", "notes" => "hey" }
68
+
69
+ expect(exception_description.exception_data).to eq( expected)
70
+ end
71
+
72
+ it "match multiple email addresses" do
73
+ mobi = "ExceptionHandling::Warning: LoginAttempt::IPAddressLocked: failed login for 'mcc@mobistreak.com'"
74
+ credit = "ExceptionHandling::Warning: LoginAttempt::IPAddressLocked: failed login for 'damon@thecreditpros.com'"
75
+
76
+ exception_description = ExceptionDescription.new(:filter1, error: "ExceptionHandling::Warning: LoginAttempt::IPAddressLocked: failed login for '(mcc\@mobistreak|damon\@thecreditpros).com'")
77
+ expect(exception_description.match?(error: mobi)).to be_truthy
78
+ expect(exception_description.match?(error: credit)).to be_truthy
79
+ end
80
+ end
81
+ end
82
+ end