exception_handling 2.7.0.pre.1 → 2.7.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.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.jenkins/Jenkinsfile +24 -8
- data/.rspec +3 -0
- data/CHANGELOG.md +2 -2
- data/Gemfile +4 -4
- data/Gemfile.lock +65 -56
- data/Rakefile +7 -6
- data/gemfiles/rails_4.gemfile +4 -4
- data/gemfiles/rails_5.gemfile +4 -4
- data/gemfiles/rails_6.gemfile +4 -4
- data/lib/exception_handling/log_stub_error.rb +2 -1
- data/lib/exception_handling/logging_methods.rb +1 -7
- data/lib/exception_handling/version.rb +1 -1
- data/{test → spec}/helpers/controller_helpers.rb +0 -0
- data/{test → spec}/helpers/exception_helpers.rb +2 -2
- data/{test → spec}/rake_test_warning_false.rb +0 -0
- data/{test/test_helper.rb → spec/spec_helper.rb} +50 -39
- data/spec/unit/exception_handling/exception_catalog_spec.rb +85 -0
- data/spec/unit/exception_handling/exception_description_spec.rb +82 -0
- data/{test/unit/exception_handling/exception_info_test.rb → spec/unit/exception_handling/exception_info_spec.rb} +105 -107
- data/{test/unit/exception_handling/honeybadger_callbacks_test.rb → spec/unit/exception_handling/honeybadger_callbacks_spec.rb} +20 -20
- data/{test/unit/exception_handling/log_error_stub_test.rb → spec/unit/exception_handling/log_error_stub_spec.rb} +38 -22
- data/{test/unit/exception_handling/logging_methods_test.rb → spec/unit/exception_handling/logging_methods_spec.rb} +8 -7
- data/{test/unit/exception_handling/mailer_test.rb → spec/unit/exception_handling/mailer_spec.rb} +17 -17
- data/spec/unit/exception_handling/methods_spec.rb +105 -0
- data/spec/unit/exception_handling/sensu_spec.rb +51 -0
- data/{test/unit/exception_handling_test.rb → spec/unit/exception_handling_spec.rb} +325 -329
- metadata +36 -35
- data/test/unit/exception_handling/exception_catalog_test.rb +0 -85
- data/test/unit/exception_handling/exception_description_test.rb +0 -82
- data/test/unit/exception_handling/methods_test.rb +0 -105
- data/test/unit/exception_handling/sensu_test.rb +0 -52
File without changes
|
@@ -1,17 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
require '
|
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
|
-
|
94
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
131
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
159
|
+
expect(true).to be_truthy # To keep the assertion count accurate
|
181
160
|
else
|
182
|
-
|
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
|
@@ -1,58 +1,58 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require File.expand_path('../../
|
3
|
+
require File.expand_path('../../spec_helper', __dir__)
|
4
4
|
require_test_helper 'controller_helpers'
|
5
5
|
require_test_helper 'exception_helpers'
|
6
6
|
|
7
7
|
module ExceptionHandling
|
8
|
-
|
8
|
+
describe ExceptionInfo do
|
9
9
|
include ControllerHelpers
|
10
10
|
include ExceptionHelpers
|
11
11
|
|
12
12
|
context "initialize" do
|
13
|
-
|
13
|
+
before do
|
14
14
|
@exception = StandardError.new("something went wrong")
|
15
15
|
@timestamp = Time.now
|
16
16
|
@controller = Object.new
|
17
17
|
end
|
18
18
|
|
19
19
|
context "controller_from_context" do
|
20
|
-
|
20
|
+
it "extract controller from context when not specified explicitly" do
|
21
21
|
exception_context = {
|
22
22
|
"action_controller.instance" => @controller
|
23
23
|
}
|
24
24
|
exception_info = ExceptionInfo.new(@exception, exception_context, @timestamp)
|
25
|
-
|
25
|
+
expect(exception_info.controller).to eq(@controller)
|
26
26
|
end
|
27
27
|
|
28
|
-
|
28
|
+
it "prefer the explicit controller over the one from context" do
|
29
29
|
exception_context = {
|
30
30
|
"action_controller.instance" => Object.new
|
31
31
|
}
|
32
32
|
exception_info = ExceptionInfo.new(@exception, exception_context, @timestamp, controller: @controller)
|
33
|
-
|
34
|
-
|
33
|
+
expect(exception_info.controller).to eq(@controller)
|
34
|
+
expect(exception_info.controller).not_to eq(exception_context["action_controller.instance"])
|
35
35
|
end
|
36
36
|
|
37
|
-
|
37
|
+
it "leave controller unset when not included in the context hash" do
|
38
38
|
exception_info = ExceptionInfo.new(@exception, {}, @timestamp)
|
39
|
-
|
39
|
+
expect(exception_info.controller).to be_nil
|
40
40
|
end
|
41
41
|
|
42
|
-
|
42
|
+
it "leave controller unset when context is not in hash format" do
|
43
43
|
exception_info = ExceptionInfo.new(@exception, "string context", @timestamp)
|
44
|
-
|
44
|
+
expect(exception_info.controller).to be_nil
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
49
|
context "data" do
|
50
|
-
|
50
|
+
before do
|
51
51
|
@exception = StandardError.new("something went wrong")
|
52
52
|
@timestamp = Time.now
|
53
53
|
end
|
54
54
|
|
55
|
-
|
55
|
+
it "return a hash with exception specific data including context hash" do
|
56
56
|
exception_context = {
|
57
57
|
"rack.session" => {
|
58
58
|
user_id: 23,
|
@@ -73,17 +73,17 @@ module ExceptionHandling
|
|
73
73
|
}
|
74
74
|
}
|
75
75
|
|
76
|
-
|
76
|
+
expect(exception_info.data).to eq(expected_data)
|
77
77
|
end
|
78
78
|
|
79
|
-
|
79
|
+
it "generate exception data appropriately if exception message is nil" do
|
80
80
|
exception_info = ExceptionInfo.new(exception_with_nil_message, "custom context data", @timestamp)
|
81
81
|
exception_data = exception_info.data
|
82
|
-
|
83
|
-
|
82
|
+
expect(exception_data["error_string"]).to eq("RuntimeError: ")
|
83
|
+
expect(exception_data["error"]).to eq("RuntimeError: : custom context data")
|
84
84
|
end
|
85
85
|
|
86
|
-
|
86
|
+
it "return a hash with exception specific data including context string" do
|
87
87
|
exception_context = "custom context data"
|
88
88
|
exception_info = ExceptionInfo.new(@exception, exception_context, @timestamp)
|
89
89
|
expected_data = {
|
@@ -96,11 +96,10 @@ module ExceptionHandling
|
|
96
96
|
"message" => "custom context data"
|
97
97
|
}
|
98
98
|
}
|
99
|
-
|
100
|
-
assert_equal_with_diff expected_data, exception_info.data
|
99
|
+
expect(exception_info.data).to eq(expected_data)
|
101
100
|
end
|
102
101
|
|
103
|
-
|
102
|
+
it "not include enhanced data from controller or custom data callback" do
|
104
103
|
env = { server: "fe98" }
|
105
104
|
parameters = { advertiser_id: 435 }
|
106
105
|
session = { username: "jsmith" }
|
@@ -109,8 +108,8 @@ module ExceptionHandling
|
|
109
108
|
data_callback = ->(data) { data[:custom_section] = "value" }
|
110
109
|
exception_info = ExceptionInfo.new(@exception, "custom context data", @timestamp, controller: controller, data_callback: data_callback)
|
111
110
|
|
112
|
-
|
113
|
-
|
111
|
+
expect(exception_info).to_not receive(:extract_and_merge_controller_data)
|
112
|
+
expect(exception_info).to_not receive(:customize_from_data_callback)
|
114
113
|
expected_data = {
|
115
114
|
"error_class" => "StandardError",
|
116
115
|
"error_string" => "StandardError: something went wrong",
|
@@ -122,12 +121,12 @@ module ExceptionHandling
|
|
122
121
|
}
|
123
122
|
}
|
124
123
|
|
125
|
-
|
124
|
+
expect(exception_info.data).to eq(expected_data)
|
126
125
|
end
|
127
126
|
end
|
128
127
|
|
129
128
|
context "enhanced_data" do
|
130
|
-
|
129
|
+
before do
|
131
130
|
@exception = StandardError.new("something went wrong")
|
132
131
|
@timestamp = Time.now
|
133
132
|
@exception_context = {
|
@@ -145,13 +144,13 @@ module ExceptionHandling
|
|
145
144
|
@data_callback = ->(data) { data[:custom_section] = "check this out" }
|
146
145
|
end
|
147
146
|
|
148
|
-
|
147
|
+
it "not return a mutable object for the session" do
|
149
148
|
exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp)
|
150
149
|
exception_info.enhanced_data["session"]["hello"] = "world"
|
151
|
-
|
150
|
+
expect(@controller.session["hello"]).to be_nil
|
152
151
|
end
|
153
152
|
|
154
|
-
|
153
|
+
it "return a hash with generic exception attributes as well as context data" do
|
155
154
|
exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp)
|
156
155
|
expected_data = {
|
157
156
|
"error_class" => "StandardError",
|
@@ -164,19 +163,19 @@ module ExceptionHandling
|
|
164
163
|
"location" => { "file" => "<no backtrace>", "line" => nil }
|
165
164
|
}
|
166
165
|
|
167
|
-
|
166
|
+
expect(prepare_data(exception_info.enhanced_data)).to eq(expected_data)
|
168
167
|
end
|
169
168
|
|
170
|
-
|
169
|
+
it "generate exception data appropriately if exception message is nil" do
|
171
170
|
exception_with_nil_message = RuntimeError.new(nil)
|
172
|
-
|
171
|
+
allow(exception_with_nil_message).to receive(:message).and_return(nil)
|
173
172
|
exception_info = ExceptionInfo.new(exception_with_nil_message, @exception_context, @timestamp)
|
174
173
|
exception_data = exception_info.enhanced_data
|
175
|
-
|
176
|
-
|
174
|
+
expect(exception_data["error_string"]).to eq("RuntimeError: ")
|
175
|
+
expect(exception_data["error"]).to eq("RuntimeError: ")
|
177
176
|
end
|
178
177
|
|
179
|
-
|
178
|
+
it "include controller data when available" do
|
180
179
|
exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp, controller: @controller)
|
181
180
|
expected_data = {
|
182
181
|
"error_class" => "StandardError",
|
@@ -194,10 +193,10 @@ module ExceptionHandling
|
|
194
193
|
"location" => { "controller" => "dummy", "action" => "fail", "file" => "<no backtrace>", "line" => nil }
|
195
194
|
}
|
196
195
|
|
197
|
-
|
196
|
+
expect(prepare_data(exception_info.enhanced_data)).to eq(expected_data)
|
198
197
|
end
|
199
198
|
|
200
|
-
|
199
|
+
it "extract controller from rack specific exception context when not provided explicitly" do
|
201
200
|
@exception_context["action_controller.instance"] = @controller
|
202
201
|
exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp)
|
203
202
|
expected_data = {
|
@@ -216,10 +215,10 @@ module ExceptionHandling
|
|
216
215
|
"location" => { "controller" => "dummy", "action" => "fail", "file" => "<no backtrace>", "line" => nil }
|
217
216
|
}
|
218
217
|
|
219
|
-
|
218
|
+
expect(prepare_data(exception_info.enhanced_data)).to eq(expected_data)
|
220
219
|
end
|
221
220
|
|
222
|
-
|
221
|
+
it "add to_s attribute to specific sections that have their content in hash format" do
|
223
222
|
exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp, controller: @controller)
|
224
223
|
expected_data = {
|
225
224
|
"error_class" => "StandardError",
|
@@ -245,10 +244,10 @@ module ExceptionHandling
|
|
245
244
|
"location" => { "controller" => "dummy", "action" => "fail", "file" => "<no backtrace>", "line" => nil }
|
246
245
|
}
|
247
246
|
|
248
|
-
|
247
|
+
expect(exception_info.enhanced_data).to eq(expected_data)
|
249
248
|
end
|
250
249
|
|
251
|
-
|
250
|
+
it "filter out sensitive parameters like passwords" do
|
252
251
|
@controller.request.parameters[:password] = "super_secret"
|
253
252
|
@controller.request.parameters[:user] = { "password" => "also super secret", "password_confirmation" => "also super secret" }
|
254
253
|
exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp, controller: @controller)
|
@@ -261,10 +260,10 @@ module ExceptionHandling
|
|
261
260
|
"password_confirmation" => "[FILTERED]"
|
262
261
|
}
|
263
262
|
}
|
264
|
-
|
263
|
+
expect(exception_info.enhanced_data["request"]["params"]).to eq(expected_params)
|
265
264
|
end
|
266
265
|
|
267
|
-
|
266
|
+
it "include the changes from the custom data callback" do
|
268
267
|
exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp, controller: nil, data_callback: @data_callback)
|
269
268
|
expected_data = {
|
270
269
|
"error_class" => "StandardError",
|
@@ -278,11 +277,11 @@ module ExceptionHandling
|
|
278
277
|
"location" => { "file" => "<no backtrace>", "line" => nil }
|
279
278
|
}
|
280
279
|
|
281
|
-
|
280
|
+
expect(prepare_data(exception_info.enhanced_data)).to eq(expected_data)
|
282
281
|
end
|
283
282
|
|
284
|
-
|
285
|
-
|
283
|
+
it "apply the custom_data_hook results" do
|
284
|
+
allow(ExceptionHandling).to receive(:custom_data_hook).and_return(->(data) { data[:custom_hook] = "changes from custom hook" })
|
286
285
|
exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp)
|
287
286
|
expected_data = {
|
288
287
|
"error_class" => "StandardError",
|
@@ -296,35 +295,35 @@ module ExceptionHandling
|
|
296
295
|
"location" => { "file" => "<no backtrace>", "line" => nil }
|
297
296
|
}
|
298
297
|
|
299
|
-
|
298
|
+
expect(prepare_data(exception_info.enhanced_data)).to eq(expected_data)
|
300
299
|
end
|
301
300
|
|
302
|
-
|
301
|
+
it "log info if the custom data hook results in a nil message exception" do
|
303
302
|
ExceptionHandling.custom_data_hook = ->(_data) do
|
304
303
|
raise_exception_with_nil_message
|
305
304
|
end
|
306
305
|
log_info_messages = []
|
307
|
-
|
306
|
+
allow(ExceptionHandling.logger).to receive(:info).with(any_args) do |message, _|
|
308
307
|
log_info_messages << message
|
309
308
|
end
|
310
309
|
|
311
310
|
exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp)
|
312
311
|
exception_info.enhanced_data
|
313
|
-
|
312
|
+
expect(log_info_messages.find { |message| message =~ /Unable to execute custom custom_data_hook callback/ }).to be_truthy
|
314
313
|
ExceptionHandling.custom_data_hook = nil
|
315
314
|
end
|
316
315
|
end
|
317
316
|
|
318
317
|
context "exception_description" do
|
319
|
-
|
318
|
+
it "return the exception description from the global exception filter list" do
|
320
319
|
exception = StandardError.new("No route matches")
|
321
320
|
exception_info = ExceptionInfo.new(exception, {}, Time.now)
|
322
321
|
description = exception_info.exception_description
|
323
|
-
|
324
|
-
|
322
|
+
expect(description).to_not be_nil
|
323
|
+
expect(description.filter_name).to eq(:NoRoute)
|
325
324
|
end
|
326
325
|
|
327
|
-
|
326
|
+
it "find the description when filter criteria includes section in hash format" do
|
328
327
|
env = { server: "fe98" }
|
329
328
|
parameters = { advertiser_id: 435, controller: "sessions", action: "fail" }
|
330
329
|
session = { username: "jsmith", id: "session_key" }
|
@@ -332,25 +331,25 @@ module ExceptionHandling
|
|
332
331
|
controller = create_dummy_controller(env, parameters, session, request_uri)
|
333
332
|
exception = StandardError.new("Request to click domain rejected")
|
334
333
|
exception_info = ExceptionInfo.new(exception, nil, Time.now, controller: controller)
|
335
|
-
|
334
|
+
expect(exception_info.enhanced_data[:request].is_a?(Hash)).to eq(true)
|
336
335
|
description = exception_info.exception_description
|
337
|
-
|
338
|
-
|
336
|
+
expect(description).to_not be_nil
|
337
|
+
expect(description.filter_name).to eq(:"Click Request Rejected")
|
339
338
|
end
|
340
339
|
|
341
|
-
|
340
|
+
it "return same description object for related errors (avoid reloading exception catalog from disk)" do
|
342
341
|
exception = StandardError.new("No route matches")
|
343
342
|
exception_info = ExceptionInfo.new(exception, nil, Time.now)
|
344
343
|
description = exception_info.exception_description
|
345
344
|
|
346
345
|
repeat_ex = StandardError.new("No route matches 2")
|
347
346
|
repeat_ex_info = ExceptionInfo.new(repeat_ex, nil, Time.now)
|
348
|
-
|
347
|
+
expect(repeat_ex_info.exception_description.object_id).to eq(description.object_id)
|
349
348
|
end
|
350
349
|
end
|
351
350
|
|
352
351
|
context "controller_name" do
|
353
|
-
|
352
|
+
before do
|
354
353
|
@exception = StandardError.new('something went wrong')
|
355
354
|
@timestamp = Time.now
|
356
355
|
@exception_context = {
|
@@ -362,7 +361,7 @@ module ExceptionHandling
|
|
362
361
|
}
|
363
362
|
end
|
364
363
|
|
365
|
-
|
364
|
+
it "return controller_name when controller is present" do
|
366
365
|
env = { server: 'fe98' }
|
367
366
|
parameters = { controller: 'some_controller' }
|
368
367
|
session = { username: 'smith' }
|
@@ -370,85 +369,85 @@ module ExceptionHandling
|
|
370
369
|
controller = create_dummy_controller(env, parameters, session, request_uri)
|
371
370
|
exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp, controller: controller)
|
372
371
|
|
373
|
-
|
372
|
+
expect(exception_info.controller_name).to eq('some_controller')
|
374
373
|
end
|
375
374
|
|
376
|
-
|
375
|
+
it "return an empty string when controller is not present" do
|
377
376
|
exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp)
|
378
377
|
|
379
|
-
|
378
|
+
expect(exception_info.controller_name).to eq('')
|
380
379
|
end
|
381
380
|
end
|
382
381
|
|
383
382
|
context "send_to_honeybadger?" do
|
384
|
-
|
385
|
-
|
383
|
+
it "be enabled when Honeybadger is defined and exception is not in the filter list" do
|
384
|
+
allow(ExceptionHandling).to receive(:honeybadger_defined?).and_return(true)
|
386
385
|
exception = StandardError.new("something went wrong")
|
387
386
|
exception_info = ExceptionInfo.new(exception, nil, Time.now)
|
388
|
-
|
389
|
-
|
387
|
+
expect(exception_info.exception_description).to be_nil
|
388
|
+
expect(exception_info.send_to_honeybadger?).to eq(true)
|
390
389
|
end
|
391
390
|
|
392
|
-
|
393
|
-
|
391
|
+
it "be enabled when Honeybadger is defined and exception is on the filter list with the flag turned on" do
|
392
|
+
allow(ExceptionHandling).to receive(:honeybadger_defined?).and_return(true)
|
394
393
|
exception = StandardError.new("No route matches")
|
395
394
|
exception_info = ExceptionInfo.new(exception, nil, Time.now)
|
396
|
-
|
397
|
-
|
398
|
-
|
395
|
+
expect(exception_info.exception_description).to_not be_nil
|
396
|
+
expect(exception_info.exception_description.send_to_honeybadger).to eq(true)
|
397
|
+
expect(exception_info.send_to_honeybadger?).to eq(true)
|
399
398
|
end
|
400
399
|
|
401
|
-
|
402
|
-
|
400
|
+
it "be disabled when Honeybadger is defined and exception is on the filter list with the flag turned off" do
|
401
|
+
allow(ExceptionHandling).to receive(:honeybadger_defined?).and_return(true)
|
403
402
|
exception = StandardError.new("No route matches")
|
404
403
|
exception_info = ExceptionInfo.new(exception, nil, Time.now)
|
405
|
-
|
406
|
-
|
407
|
-
|
404
|
+
expect(exception_info.exception_description).to_not be_nil
|
405
|
+
allow(exception_info.exception_description).to receive(:send_to_honeybadger).and_return(false)
|
406
|
+
expect(exception_info.send_to_honeybadger?).to eq(false)
|
408
407
|
end
|
409
408
|
|
410
|
-
|
411
|
-
|
409
|
+
it "be disabled when Honeybadger is not defined" do
|
410
|
+
allow(ExceptionHandling).to receive(:honeybadger_defined?).and_return(false)
|
412
411
|
exception = StandardError.new("something went wrong")
|
413
412
|
exception_info = ExceptionInfo.new(exception, nil, Time.now)
|
414
|
-
|
415
|
-
|
413
|
+
expect(exception_info.exception_description).to be_nil
|
414
|
+
expect(exception_info.send_to_honeybadger?).to eq(false)
|
416
415
|
end
|
417
416
|
end
|
418
417
|
|
419
418
|
context "honeybadger_context_data" do
|
420
|
-
|
421
|
-
|
419
|
+
before do
|
420
|
+
allow(ExceptionHandling.logger).to receive(:current_context_for_thread).and_return({ cuid: 'ABCD' })
|
422
421
|
end
|
423
422
|
|
424
|
-
|
423
|
+
it "include thread_context when log_context: is nil" do
|
425
424
|
exception_with_nil_message = RuntimeError.new(nil)
|
426
|
-
|
425
|
+
allow(exception_with_nil_message).to receive(:message).and_return(nil)
|
427
426
|
exception_info = ExceptionInfo.new(exception_with_nil_message, @exception_context, @timestamp)
|
428
427
|
honeybadger_context_data = exception_info.honeybadger_context_data
|
429
|
-
|
428
|
+
expect(honeybadger_context_data[:log_context]).to eq({ "cuid" => 'ABCD' })
|
430
429
|
end
|
431
430
|
|
432
|
-
|
431
|
+
it "include thread context merged with log_context:" do
|
433
432
|
exception_with_nil_message = RuntimeError.new(nil)
|
434
|
-
|
433
|
+
allow(exception_with_nil_message).to receive(:message).and_return(nil)
|
435
434
|
exception_info = ExceptionInfo.new(exception_with_nil_message, @exception_context, @timestamp, log_context: { url: 'http://example.com' })
|
436
435
|
honeybadger_context_data = exception_info.honeybadger_context_data
|
437
|
-
|
436
|
+
expect(honeybadger_context_data[:log_context]).to eq({ "cuid" => 'ABCD', "url" => 'http://example.com' })
|
438
437
|
end
|
439
438
|
|
440
|
-
|
439
|
+
it "return the error details and relevant context data to be used as honeybadger notification context while filtering sensitive data" do
|
441
440
|
env = { server: "fe98" }
|
442
441
|
parameters = { advertiser_id: 435 }
|
443
442
|
session = { username: "jsmith" }
|
444
443
|
request_uri = "host/path"
|
445
444
|
controller = create_dummy_controller(env, parameters, session, request_uri)
|
446
|
-
|
445
|
+
allow(ExceptionHandling).to receive(:server_name).and_return("invoca_fe98")
|
447
446
|
|
448
447
|
exception = StandardError.new("Some Exception")
|
449
448
|
exception.set_backtrace([
|
450
|
-
"
|
451
|
-
"
|
449
|
+
"spec/unit/exception_handling_test.rb:847:in `exception_1'",
|
450
|
+
"spec/unit/exception_handling_test.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
|
452
451
|
])
|
453
452
|
exception_context = { "SERVER_NAME" => "exceptional.com" }
|
454
453
|
data_callback = ->(data) do
|
@@ -466,7 +465,6 @@ module ExceptionHandling
|
|
466
465
|
exception_context: { "SERVER_NAME" => "exceptional.com" },
|
467
466
|
server: "invoca_fe98",
|
468
467
|
scm_revision: "5b24eac37aaa91f5784901e9aabcead36fd9df82",
|
469
|
-
notes: "this is used by a test",
|
470
468
|
user_details: { "username" => "jsmith" },
|
471
469
|
request: {
|
472
470
|
"params" => { "advertiser_id" => 435 },
|
@@ -481,29 +479,30 @@ module ExceptionHandling
|
|
481
479
|
"SERVER_NAME" => "exceptional.com"
|
482
480
|
},
|
483
481
|
backtrace: [
|
484
|
-
"
|
485
|
-
"
|
482
|
+
"spec/unit/exception_handling_test.rb:847:in `exception_1'",
|
483
|
+
"spec/unit/exception_handling_test.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
|
486
484
|
],
|
487
485
|
event_response: "Event successfully received",
|
488
|
-
log_context: { "cuid" => "ABCD" }
|
486
|
+
log_context: { "cuid" => "ABCD" },
|
487
|
+
notes: "this is used by a test"
|
489
488
|
}
|
490
|
-
|
489
|
+
expect(exception_info.honeybadger_context_data).to eq(expected_data)
|
491
490
|
end
|
492
491
|
|
493
492
|
[['Hash', { 'cookie' => 'cookie_context' }],
|
494
493
|
['String', 'Entering Error State'],
|
495
494
|
['Array', ['Error1', 'Error2']]].each do |klass, value|
|
496
|
-
|
495
|
+
it "extract context from exception_context when it is a #{klass}" do
|
497
496
|
exception = StandardError.new("Exception")
|
498
497
|
exception_context = value
|
499
498
|
exception_info = ExceptionInfo.new(exception, exception_context, Time.now)
|
500
499
|
|
501
|
-
|
502
|
-
|
500
|
+
expect(value.class.name).to eq(klass)
|
501
|
+
expect(exception_info.honeybadger_context_data[:exception_context]).to eq(value)
|
503
502
|
end
|
504
503
|
end
|
505
504
|
|
506
|
-
|
505
|
+
it "filter out sensitive data from exception context such as [password, password_confirmation, oauth_token]" do
|
507
506
|
sensitive_data = {
|
508
507
|
"password" => "super_secret",
|
509
508
|
"password_confirmation" => "super_secret_confirmation",
|
@@ -538,15 +537,14 @@ module ExceptionHandling
|
|
538
537
|
}
|
539
538
|
}.merge(expected_sensitive_data)
|
540
539
|
|
541
|
-
|
540
|
+
expect(exception_info.honeybadger_context_data[:exception_context]).to eq(expected_exception_context)
|
542
541
|
end
|
543
542
|
|
544
|
-
|
543
|
+
it "omit context if exception_context is empty" do
|
545
544
|
exception = StandardError.new("Exception")
|
546
545
|
exception_context = ""
|
547
546
|
exception_info = ExceptionInfo.new(exception, exception_context, Time.now)
|
548
|
-
|
549
|
-
assert_nil exception_info.honeybadger_context_data[:exception_context]
|
547
|
+
expect(exception_info.honeybadger_context_data[:exception_context]).to be_nil
|
550
548
|
end
|
551
549
|
end
|
552
550
|
|