exception_notification 4.2.1 → 4.4.3
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 +5 -5
- data/Appraisals +4 -3
- data/CHANGELOG.rdoc +57 -1
- data/CONTRIBUTING.md +21 -2
- data/Gemfile +3 -1
- data/README.md +105 -780
- data/Rakefile +4 -2
- data/docs/notifiers/campfire.md +50 -0
- data/docs/notifiers/custom.md +42 -0
- data/docs/notifiers/datadog.md +51 -0
- data/docs/notifiers/email.md +195 -0
- data/docs/notifiers/google_chat.md +31 -0
- data/docs/notifiers/hipchat.md +66 -0
- data/docs/notifiers/irc.md +97 -0
- data/docs/notifiers/mattermost.md +115 -0
- data/docs/notifiers/slack.md +161 -0
- data/docs/notifiers/sns.md +37 -0
- data/docs/notifiers/teams.md +54 -0
- data/docs/notifiers/webhook.md +60 -0
- data/examples/sample_app.rb +56 -0
- data/examples/sinatra/Gemfile +8 -6
- data/examples/sinatra/config.ru +3 -1
- data/examples/sinatra/sinatra_app.rb +19 -11
- data/exception_notification.gemspec +30 -23
- data/gemfiles/rails4_0.gemfile +1 -2
- data/gemfiles/rails4_1.gemfile +1 -2
- data/gemfiles/rails4_2.gemfile +1 -2
- data/gemfiles/rails5_0.gemfile +1 -2
- data/gemfiles/rails5_1.gemfile +7 -0
- data/gemfiles/rails5_2.gemfile +7 -0
- data/gemfiles/rails6_0.gemfile +7 -0
- data/lib/exception_notification.rb +3 -0
- data/lib/exception_notification/rack.rb +30 -23
- data/lib/exception_notification/rails.rb +3 -0
- data/lib/exception_notification/resque.rb +10 -10
- data/lib/exception_notification/sidekiq.rb +10 -12
- data/lib/exception_notification/version.rb +5 -0
- data/lib/exception_notifier.rb +79 -11
- data/lib/exception_notifier/base_notifier.rb +10 -5
- data/lib/exception_notifier/campfire_notifier.rb +14 -9
- data/lib/exception_notifier/datadog_notifier.rb +156 -0
- data/lib/exception_notifier/email_notifier.rb +78 -87
- data/lib/exception_notifier/google_chat_notifier.rb +44 -0
- data/lib/exception_notifier/hipchat_notifier.rb +16 -10
- data/lib/exception_notifier/irc_notifier.rb +38 -31
- data/lib/exception_notifier/mattermost_notifier.rb +54 -131
- data/lib/exception_notifier/modules/backtrace_cleaner.rb +2 -2
- data/lib/exception_notifier/modules/error_grouping.rb +87 -0
- data/lib/exception_notifier/modules/formatter.rb +121 -0
- data/lib/exception_notifier/notifier.rb +9 -6
- data/lib/exception_notifier/slack_notifier.rb +71 -40
- data/lib/exception_notifier/sns_notifier.rb +86 -0
- data/lib/exception_notifier/teams_notifier.rb +200 -0
- data/lib/exception_notifier/views/exception_notifier/_backtrace.html.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/_environment.text.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/_request.text.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/background_exception_notification.text.erb +9 -9
- data/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb +2 -4
- data/lib/exception_notifier/views/exception_notifier/exception_notification.text.erb +2 -2
- data/lib/exception_notifier/webhook_notifier.rb +17 -14
- data/lib/generators/exception_notification/install_generator.rb +11 -5
- data/lib/generators/exception_notification/templates/{exception_notification.rb → exception_notification.rb.erb} +13 -11
- data/test/exception_notification/rack_test.rb +90 -4
- data/test/exception_notification/resque_test.rb +54 -0
- data/test/exception_notifier/campfire_notifier_test.rb +59 -38
- data/test/exception_notifier/datadog_notifier_test.rb +153 -0
- data/test/exception_notifier/email_notifier_test.rb +279 -145
- data/test/exception_notifier/google_chat_notifier_test.rb +185 -0
- data/test/exception_notifier/hipchat_notifier_test.rb +105 -64
- data/test/exception_notifier/irc_notifier_test.rb +48 -30
- data/test/exception_notifier/mattermost_notifier_test.rb +218 -55
- data/test/exception_notifier/modules/error_grouping_test.rb +167 -0
- data/test/exception_notifier/modules/formatter_test.rb +152 -0
- data/test/exception_notifier/sidekiq_test.rb +9 -17
- data/test/exception_notifier/slack_notifier_test.rb +84 -62
- data/test/exception_notifier/sns_notifier_test.rb +123 -0
- data/test/exception_notifier/teams_notifier_test.rb +92 -0
- data/test/exception_notifier/webhook_notifier_test.rb +52 -48
- data/test/exception_notifier_test.rb +220 -37
- data/test/support/exception_notifier_helper.rb +14 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.html.erb +0 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.text.erb +0 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_section.html.erb +0 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_section.text.erb +0 -0
- data/test/test_helper.rb +14 -13
- metadata +154 -162
- data/test/dummy/.gitignore +0 -4
- data/test/dummy/Rakefile +0 -7
- data/test/dummy/app/controllers/application_controller.rb +0 -3
- data/test/dummy/app/controllers/posts_controller.rb +0 -30
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/helpers/posts_helper.rb +0 -2
- data/test/dummy/app/models/post.rb +0 -2
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/app/views/posts/_form.html.erb +0 -0
- data/test/dummy/app/views/posts/new.html.erb +0 -0
- data/test/dummy/app/views/posts/show.html.erb +0 -0
- data/test/dummy/config.ru +0 -4
- data/test/dummy/config/application.rb +0 -42
- data/test/dummy/config/boot.rb +0 -6
- data/test/dummy/config/database.yml +0 -22
- data/test/dummy/config/environment.rb +0 -17
- data/test/dummy/config/environments/development.rb +0 -25
- data/test/dummy/config/environments/production.rb +0 -50
- data/test/dummy/config/environments/test.rb +0 -35
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/inflections.rb +0 -10
- data/test/dummy/config/initializers/mime_types.rb +0 -5
- data/test/dummy/config/initializers/secret_token.rb +0 -8
- data/test/dummy/config/initializers/session_store.rb +0 -8
- data/test/dummy/config/locales/en.yml +0 -5
- data/test/dummy/config/routes.rb +0 -3
- data/test/dummy/db/migrate/20110729022608_create_posts.rb +0 -15
- data/test/dummy/db/schema.rb +0 -24
- data/test/dummy/db/seeds.rb +0 -7
- data/test/dummy/lib/tasks/.gitkeep +0 -0
- data/test/dummy/public/404.html +0 -26
- data/test/dummy/public/422.html +0 -26
- data/test/dummy/public/500.html +0 -26
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/images/rails.png +0 -0
- data/test/dummy/public/index.html +0 -239
- data/test/dummy/public/javascripts/application.js +0 -2
- data/test/dummy/public/javascripts/controls.js +0 -965
- data/test/dummy/public/javascripts/dragdrop.js +0 -974
- data/test/dummy/public/javascripts/effects.js +0 -1123
- data/test/dummy/public/javascripts/prototype.js +0 -6001
- data/test/dummy/public/javascripts/rails.js +0 -191
- data/test/dummy/public/robots.txt +0 -5
- data/test/dummy/public/stylesheets/.gitkeep +0 -0
- data/test/dummy/public/stylesheets/scaffold.css +0 -56
- data/test/dummy/script/rails +0 -6
- data/test/dummy/test/functional/posts_controller_test.rb +0 -218
- data/test/dummy/test/test_helper.rb +0 -7
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
|
|
5
|
+
class ErrorGroupTest < ActiveSupport::TestCase
|
|
6
|
+
setup do
|
|
7
|
+
module TestModule
|
|
8
|
+
include ExceptionNotifier::ErrorGrouping
|
|
9
|
+
@@error_grouping_cache = ActiveSupport::Cache::FileStore.new('test/dummy/tmp/non_default_location')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
@exception = RuntimeError.new('ERROR')
|
|
13
|
+
@exception.stubs(:backtrace).returns(['/path/where/error/raised:1'])
|
|
14
|
+
|
|
15
|
+
@exception2 = RuntimeError.new('ERROR2')
|
|
16
|
+
@exception2.stubs(:backtrace).returns(['/path/where/error/found:2'])
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
teardown do
|
|
20
|
+
TestModule.error_grouping_cache.clear
|
|
21
|
+
TestModule.fallback_cache_store.clear
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
test 'should add additional option: error_grouping' do
|
|
25
|
+
assert_respond_to TestModule, :error_grouping
|
|
26
|
+
assert_respond_to TestModule, :error_grouping=
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
test 'should set error_grouping to false default' do
|
|
30
|
+
assert_equal false, TestModule.error_grouping
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
test 'should add additional option: error_grouping_cache' do
|
|
34
|
+
assert_respond_to TestModule, :error_grouping_cache
|
|
35
|
+
assert_respond_to TestModule, :error_grouping_cache=
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
test 'should add additional option: error_grouping_period' do
|
|
39
|
+
assert_respond_to TestModule, :error_grouping_period
|
|
40
|
+
assert_respond_to TestModule, :error_grouping_period=
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
test 'shoud set error_grouping_period to 5.minutes default' do
|
|
44
|
+
assert_equal 300, TestModule.error_grouping_period
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
test 'should add additional option: notification_trigger' do
|
|
48
|
+
assert_respond_to TestModule, :notification_trigger
|
|
49
|
+
assert_respond_to TestModule, :notification_trigger=
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
test 'should return errors count nil when not same error for .error_count' do
|
|
53
|
+
assert_nil TestModule.error_count('something')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
test 'should return errors count when same error for .error_count' do
|
|
57
|
+
TestModule.error_grouping_cache.write('error_key', 13)
|
|
58
|
+
assert_equal 13, TestModule.error_count('error_key')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
test 'should fallback to memory store cache if specified cache store failed to read' do
|
|
62
|
+
TestModule.error_grouping_cache.stubs(:read).raises(RuntimeError.new('Failed to read'))
|
|
63
|
+
original_fallback = TestModule.fallback_cache_store
|
|
64
|
+
TestModule.expects(:fallback_cache_store).returns(original_fallback).at_least_once
|
|
65
|
+
|
|
66
|
+
assert_nil TestModule.error_count('something_to_read')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
test 'should save error with count for .save_error_count' do
|
|
70
|
+
count = rand(1..10)
|
|
71
|
+
|
|
72
|
+
TestModule.save_error_count('error_key', count)
|
|
73
|
+
assert_equal count, TestModule.error_grouping_cache.read('error_key')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
test 'should fallback to memory store cache if specified cache store failed to write' do
|
|
77
|
+
TestModule.error_grouping_cache.stubs(:write).raises(RuntimeError.new('Failed to write'))
|
|
78
|
+
original_fallback = TestModule.fallback_cache_store
|
|
79
|
+
TestModule.expects(:fallback_cache_store).returns(original_fallback).at_least_once
|
|
80
|
+
|
|
81
|
+
assert TestModule.save_error_count('something_to_cache', rand(1..10))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
test 'should save accumulated_errors_count into options' do
|
|
85
|
+
options = {}
|
|
86
|
+
TestModule.group_error!(@exception, options)
|
|
87
|
+
|
|
88
|
+
assert_equal 1, options[:accumulated_errors_count]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
test 'should not group error if different exception in .group_error!' do
|
|
92
|
+
options1 = {}
|
|
93
|
+
TestModule.expects(:save_error_count).with { |key, count| key.is_a?(String) && count == 1 }.times(4).returns(true)
|
|
94
|
+
TestModule.group_error!(@exception, options1)
|
|
95
|
+
|
|
96
|
+
options2 = {}
|
|
97
|
+
TestModule.group_error!(NoMethodError.new('method not found'), options2)
|
|
98
|
+
|
|
99
|
+
assert_equal 1, options1[:accumulated_errors_count]
|
|
100
|
+
assert_equal 1, options2[:accumulated_errors_count]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
test 'should not group error is same exception but different message or backtrace' do
|
|
104
|
+
options1 = {}
|
|
105
|
+
TestModule.expects(:save_error_count).with { |key, count| key.is_a?(String) && count == 1 }.times(4).returns(true)
|
|
106
|
+
TestModule.group_error!(@exception, options1)
|
|
107
|
+
|
|
108
|
+
options2 = {}
|
|
109
|
+
TestModule.group_error!(@exception2, options2)
|
|
110
|
+
|
|
111
|
+
assert_equal 1, options1[:accumulated_errors_count]
|
|
112
|
+
assert_equal 1, options2[:accumulated_errors_count]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
test 'should group error if same exception and message' do
|
|
116
|
+
options = {}
|
|
117
|
+
|
|
118
|
+
10.times do |i|
|
|
119
|
+
@exception2.stubs(:backtrace).returns(["/path:#{i}"])
|
|
120
|
+
TestModule.group_error!(@exception2, options)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
assert_equal 10, options[:accumulated_errors_count]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
test 'should group error if same exception and backtrace' do
|
|
127
|
+
options = {}
|
|
128
|
+
|
|
129
|
+
10.times do |i|
|
|
130
|
+
@exception2.stubs(:message).returns("ERRORS#{i}")
|
|
131
|
+
TestModule.group_error!(@exception2, options)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
assert_equal 10, options[:accumulated_errors_count]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
test 'should group error by that message have high priority' do
|
|
138
|
+
message_based_key = "exception:#{Zlib.crc32("RuntimeError\nmessage:ERROR")}"
|
|
139
|
+
backtrace_based_key = "exception:#{Zlib.crc32("RuntimeError\npath:/path/where/error/raised:1")}"
|
|
140
|
+
|
|
141
|
+
TestModule.save_error_count(message_based_key, 1)
|
|
142
|
+
TestModule.save_error_count(backtrace_based_key, 1)
|
|
143
|
+
|
|
144
|
+
TestModule.expects(:save_error_count).with(message_based_key, 2).once
|
|
145
|
+
TestModule.expects(:save_error_count).with(backtrace_based_key, 2).never
|
|
146
|
+
|
|
147
|
+
TestModule.group_error!(@exception, {})
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
test 'use default formula if not specify notification_trigger in .send_notification?' do
|
|
151
|
+
TestModule.stubs(:notification_trigger).returns(nil)
|
|
152
|
+
|
|
153
|
+
count = 16
|
|
154
|
+
Math.expects(:log2).with(count).returns(4)
|
|
155
|
+
|
|
156
|
+
assert TestModule.send_notification?(@exception, count)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
test 'use specified trigger in .send_notification?' do
|
|
160
|
+
trigger = proc { |_exception, count| (count % 4).zero? }
|
|
161
|
+
TestModule.stubs(:notification_trigger).returns(trigger)
|
|
162
|
+
|
|
163
|
+
count = 16
|
|
164
|
+
trigger.expects(:call).with(@exception, count).returns(true)
|
|
165
|
+
assert TestModule.send_notification?(@exception, count)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'timecop'
|
|
5
|
+
|
|
6
|
+
class FormatterTest < ActiveSupport::TestCase
|
|
7
|
+
setup do
|
|
8
|
+
@exception = RuntimeError.new('test')
|
|
9
|
+
Timecop.freeze('2018-12-09 12:07:16 UTC')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
teardown do
|
|
13
|
+
Timecop.return
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
#
|
|
17
|
+
# #title
|
|
18
|
+
#
|
|
19
|
+
test 'title returns correct content' do
|
|
20
|
+
formatter = ExceptionNotifier::Formatter.new(@exception)
|
|
21
|
+
|
|
22
|
+
title = if defined?(::Rails) && ::Rails.respond_to?(:env)
|
|
23
|
+
'⚠️ Error occurred in test ⚠️'
|
|
24
|
+
else
|
|
25
|
+
'⚠️ Error occurred ⚠️'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
assert_equal title, formatter.title
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#
|
|
32
|
+
# #subtitle
|
|
33
|
+
#
|
|
34
|
+
test 'subtitle without accumulated error' do
|
|
35
|
+
formatter = ExceptionNotifier::Formatter.new(@exception)
|
|
36
|
+
assert_equal 'A *RuntimeError* occurred.', formatter.subtitle
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
test 'subtitle with accumulated error' do
|
|
40
|
+
formatter = ExceptionNotifier::Formatter.new(@exception, accumulated_errors_count: 3)
|
|
41
|
+
assert_equal '3 *RuntimeError* occurred.', formatter.subtitle
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
test 'subtitle with controller' do
|
|
45
|
+
env = Rack::MockRequest.env_for(
|
|
46
|
+
'/', 'action_controller.instance' => test_controller
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
formatter = ExceptionNotifier::Formatter.new(@exception, env: env)
|
|
50
|
+
assert_equal 'A *RuntimeError* occurred in *home#index*.', formatter.subtitle
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
#
|
|
54
|
+
# #app_name
|
|
55
|
+
#
|
|
56
|
+
test 'app_name defaults to Rails app name' do
|
|
57
|
+
formatter = ExceptionNotifier::Formatter.new(@exception)
|
|
58
|
+
|
|
59
|
+
if defined?(::Rails) && ::Rails.respond_to?(:application)
|
|
60
|
+
assert_equal 'dummy', formatter.app_name
|
|
61
|
+
else
|
|
62
|
+
assert_nil formatter.app_name
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
test 'app_name can be overwritten using options' do
|
|
67
|
+
formatter = ExceptionNotifier::Formatter.new(@exception, app_name: 'test')
|
|
68
|
+
assert_equal 'test', formatter.app_name
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
#
|
|
72
|
+
# #request_message
|
|
73
|
+
#
|
|
74
|
+
test 'request_message when env set' do
|
|
75
|
+
text = [
|
|
76
|
+
'```',
|
|
77
|
+
'* url : http://test.address/?id=foo',
|
|
78
|
+
'* http_method : GET',
|
|
79
|
+
'* ip_address : 127.0.0.1',
|
|
80
|
+
'* parameters : {"id"=>"foo"}',
|
|
81
|
+
'* timestamp : 2018-12-09 12:07:16 UTC',
|
|
82
|
+
'```'
|
|
83
|
+
].join("\n")
|
|
84
|
+
|
|
85
|
+
env = Rack::MockRequest.env_for(
|
|
86
|
+
'/',
|
|
87
|
+
'HTTP_HOST' => 'test.address',
|
|
88
|
+
'REMOTE_ADDR' => '127.0.0.1',
|
|
89
|
+
params: { id: 'foo' }
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
formatter = ExceptionNotifier::Formatter.new(@exception, env: env)
|
|
93
|
+
assert_equal text, formatter.request_message
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
test 'request_message when env not set' do
|
|
97
|
+
formatter = ExceptionNotifier::Formatter.new(@exception)
|
|
98
|
+
assert_nil formatter.request_message
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
#
|
|
102
|
+
# #backtrace_message
|
|
103
|
+
#
|
|
104
|
+
test 'backtrace_message when backtrace set' do
|
|
105
|
+
text = [
|
|
106
|
+
'```',
|
|
107
|
+
"* app/controllers/my_controller.rb:53:in `my_controller_params'",
|
|
108
|
+
"* app/controllers/my_controller.rb:34:in `update'",
|
|
109
|
+
'```'
|
|
110
|
+
].join("\n")
|
|
111
|
+
|
|
112
|
+
@exception.set_backtrace([
|
|
113
|
+
"app/controllers/my_controller.rb:53:in `my_controller_params'",
|
|
114
|
+
"app/controllers/my_controller.rb:34:in `update'"
|
|
115
|
+
])
|
|
116
|
+
|
|
117
|
+
formatter = ExceptionNotifier::Formatter.new(@exception)
|
|
118
|
+
assert_equal text, formatter.backtrace_message
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
test 'backtrace_message when no backtrace' do
|
|
122
|
+
formatter = ExceptionNotifier::Formatter.new(@exception)
|
|
123
|
+
assert_nil formatter.backtrace_message
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
#
|
|
127
|
+
# #controller_and_action
|
|
128
|
+
#
|
|
129
|
+
test 'correct controller_and_action if controller is present' do
|
|
130
|
+
env = Rack::MockRequest.env_for(
|
|
131
|
+
'/', 'action_controller.instance' => test_controller
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
formatter = ExceptionNotifier::Formatter.new(@exception, env: env)
|
|
135
|
+
assert_equal 'home#index', formatter.controller_and_action
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
test 'controller_and_action is nil if no controller' do
|
|
139
|
+
env = Rack::MockRequest.env_for('/')
|
|
140
|
+
|
|
141
|
+
formatter = ExceptionNotifier::Formatter.new(@exception, env: env)
|
|
142
|
+
assert_nil formatter.controller_and_action
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def test_controller
|
|
146
|
+
controller = mock('controller')
|
|
147
|
+
controller.stubs(:action_name).returns('index')
|
|
148
|
+
controller.stubs(:controller_name).returns('home')
|
|
149
|
+
|
|
150
|
+
controller
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -1,38 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
2
4
|
|
|
3
5
|
# To allow sidekiq error handlers to be registered, sidekiq must be in
|
|
4
6
|
# "server mode". This mode is triggered by loading sidekiq/cli. Note this
|
|
5
7
|
# has to be loaded before exception_notification/sidekiq.
|
|
6
|
-
require
|
|
8
|
+
require 'sidekiq/cli'
|
|
9
|
+
require 'sidekiq/testing'
|
|
7
10
|
|
|
8
|
-
require
|
|
11
|
+
require 'exception_notification/sidekiq'
|
|
9
12
|
|
|
10
13
|
class MockSidekiqServer
|
|
11
14
|
include ::Sidekiq::ExceptionHandler
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
class SidekiqTest < ActiveSupport::TestCase
|
|
15
|
-
|
|
16
|
-
@_original_sidekiq_logger = Sidekiq::Logging.logger
|
|
17
|
-
|
|
18
|
-
# Silence sidekiq warning to stdout
|
|
19
|
-
Sidekiq::Logging.logger = nil
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
test "should call notify_exception when sidekiq raises an error" do
|
|
18
|
+
test 'should call notify_exception when sidekiq raises an error' do
|
|
23
19
|
server = MockSidekiqServer.new
|
|
24
|
-
message =
|
|
20
|
+
message = {}
|
|
25
21
|
exception = RuntimeError.new
|
|
26
22
|
|
|
27
23
|
ExceptionNotifier.expects(:notify_exception).with(
|
|
28
24
|
exception,
|
|
29
|
-
:
|
|
25
|
+
data: { sidekiq: message }
|
|
30
26
|
)
|
|
31
27
|
|
|
32
28
|
server.handle_exception(exception, message)
|
|
33
29
|
end
|
|
34
|
-
|
|
35
|
-
teardown do
|
|
36
|
-
Sidekiq::Logging.logger = @_original_sidekiq_logger
|
|
37
|
-
end
|
|
38
30
|
end
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'test_helper'
|
|
2
4
|
require 'slack-notifier'
|
|
3
5
|
|
|
4
6
|
class SlackNotifierTest < ActiveSupport::TestCase
|
|
5
|
-
|
|
6
7
|
def setup
|
|
7
8
|
@exception = fake_exception
|
|
8
9
|
@exception.stubs(:backtrace).returns(fake_backtrace)
|
|
9
10
|
@exception.stubs(:message).returns('exception message')
|
|
11
|
+
ExceptionNotifier::SlackNotifier.any_instance.stubs(:clean_backtrace).returns(fake_cleaned_backtrace)
|
|
10
12
|
Socket.stubs(:gethostname).returns('example.com')
|
|
11
13
|
end
|
|
12
14
|
|
|
13
|
-
test
|
|
15
|
+
test 'should send a slack notification if properly configured' do
|
|
14
16
|
options = {
|
|
15
|
-
webhook_url:
|
|
17
|
+
webhook_url: 'http://slack.webhook.url'
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
Slack::Notifier.any_instance.expects(:ping).with('', fake_notification)
|
|
@@ -21,9 +23,9 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
21
23
|
slack_notifier.call(@exception)
|
|
22
24
|
end
|
|
23
25
|
|
|
24
|
-
test
|
|
26
|
+
test 'should send a slack notification without backtrace info if properly configured' do
|
|
25
27
|
options = {
|
|
26
|
-
webhook_url:
|
|
28
|
+
webhook_url: 'http://slack.webhook.url'
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
Slack::Notifier.any_instance.expects(:ping).with('', fake_notification(fake_exception_without_backtrace))
|
|
@@ -32,10 +34,10 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
32
34
|
slack_notifier.call(fake_exception_without_backtrace)
|
|
33
35
|
end
|
|
34
36
|
|
|
35
|
-
test
|
|
37
|
+
test 'should send the notification to the specified channel' do
|
|
36
38
|
options = {
|
|
37
|
-
webhook_url:
|
|
38
|
-
channel:
|
|
39
|
+
webhook_url: 'http://slack.webhook.url',
|
|
40
|
+
channel: 'channel'
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
Slack::Notifier.any_instance.expects(:ping).with('', fake_notification)
|
|
@@ -43,13 +45,14 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
43
45
|
slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
|
|
44
46
|
slack_notifier.call(@exception)
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
channel = slack_notifier.notifier.config.defaults[:channel]
|
|
49
|
+
assert_equal channel, options[:channel]
|
|
47
50
|
end
|
|
48
51
|
|
|
49
|
-
test
|
|
52
|
+
test 'should send the notification to the specified username' do
|
|
50
53
|
options = {
|
|
51
|
-
webhook_url:
|
|
52
|
-
username:
|
|
54
|
+
webhook_url: 'http://slack.webhook.url',
|
|
55
|
+
username: 'username'
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
Slack::Notifier.any_instance.expects(:ping).with('', fake_notification)
|
|
@@ -57,12 +60,13 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
57
60
|
slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
|
|
58
61
|
slack_notifier.call(@exception)
|
|
59
62
|
|
|
60
|
-
|
|
63
|
+
username = slack_notifier.notifier.config.defaults[:username]
|
|
64
|
+
assert_equal username, options[:username]
|
|
61
65
|
end
|
|
62
66
|
|
|
63
|
-
test
|
|
67
|
+
test 'should send the notification with specific backtrace lines' do
|
|
64
68
|
options = {
|
|
65
|
-
webhook_url:
|
|
69
|
+
webhook_url: 'http://slack.webhook.url',
|
|
66
70
|
backtrace_lines: 1
|
|
67
71
|
}
|
|
68
72
|
|
|
@@ -72,17 +76,33 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
72
76
|
slack_notifier.call(@exception)
|
|
73
77
|
end
|
|
74
78
|
|
|
75
|
-
test
|
|
79
|
+
test 'should send the notification with additional fields' do
|
|
80
|
+
field = { title: 'Branch', value: 'master', short: true }
|
|
81
|
+
options = {
|
|
82
|
+
webhook_url: 'http://slack.webhook.url',
|
|
83
|
+
additional_fields: [field]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
Slack::Notifier.any_instance.expects(:ping).with('', fake_notification(@exception, {}, nil, 10, [field]))
|
|
87
|
+
|
|
88
|
+
slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
|
|
89
|
+
slack_notifier.call(@exception)
|
|
90
|
+
|
|
91
|
+
additional_fields = slack_notifier.notifier.config.defaults[:additional_fields]
|
|
92
|
+
assert_equal additional_fields, options[:additional_fields]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
test 'should pass the additional parameters to Slack::Notifier.ping' do
|
|
76
96
|
options = {
|
|
77
|
-
webhook_url:
|
|
78
|
-
username:
|
|
79
|
-
custom_hook:
|
|
97
|
+
webhook_url: 'http://slack.webhook.url',
|
|
98
|
+
username: 'test',
|
|
99
|
+
custom_hook: 'hook',
|
|
80
100
|
additional_parameters: {
|
|
81
|
-
icon_url:
|
|
101
|
+
icon_url: 'icon'
|
|
82
102
|
}
|
|
83
103
|
}
|
|
84
104
|
|
|
85
|
-
Slack::Notifier.any_instance.expects(:ping).with('', options[:additional_parameters].merge(fake_notification)
|
|
105
|
+
Slack::Notifier.any_instance.expects(:ping).with('', options[:additional_parameters].merge(fake_notification))
|
|
86
106
|
|
|
87
107
|
slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
|
|
88
108
|
slack_notifier.call(@exception)
|
|
@@ -97,55 +117,57 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
97
117
|
assert_nil slack_notifier.call(@exception)
|
|
98
118
|
end
|
|
99
119
|
|
|
100
|
-
test
|
|
120
|
+
test 'should pass along environment data' do
|
|
101
121
|
options = {
|
|
102
|
-
webhook_url:
|
|
103
|
-
ignore_data_if: lambda {|k,v|
|
|
104
|
-
|
|
122
|
+
webhook_url: 'http://slack.webhook.url',
|
|
123
|
+
ignore_data_if: lambda { |k, v|
|
|
124
|
+
k.to_s == 'key_to_be_ignored' || v.is_a?(Hash)
|
|
105
125
|
}
|
|
106
126
|
}
|
|
107
127
|
|
|
108
128
|
notification_options = {
|
|
109
129
|
env: {
|
|
110
|
-
'exception_notifier.exception_data' => {foo: 'bar', john: 'doe'}
|
|
130
|
+
'exception_notifier.exception_data' => { foo: 'bar', john: 'doe' }
|
|
111
131
|
},
|
|
112
132
|
data: {
|
|
113
|
-
'user_id'
|
|
133
|
+
'user_id' => 5,
|
|
114
134
|
'key_to_be_ignored' => 'whatever',
|
|
115
|
-
'ignore_as_well'
|
|
135
|
+
'ignore_as_well' => { what: 'ever' }
|
|
116
136
|
}
|
|
117
137
|
}
|
|
118
138
|
|
|
119
139
|
expected_data_string = "foo: bar\njohn: doe\nuser_id: 5"
|
|
120
140
|
|
|
121
|
-
Slack::Notifier.any_instance
|
|
141
|
+
Slack::Notifier.any_instance
|
|
142
|
+
.expects(:ping)
|
|
143
|
+
.with('', fake_notification(@exception, notification_options, expected_data_string))
|
|
122
144
|
slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
|
|
123
145
|
slack_notifier.call(@exception, notification_options)
|
|
124
146
|
end
|
|
125
147
|
|
|
126
|
-
test
|
|
148
|
+
test 'should call pre/post_callback proc if specified' do
|
|
127
149
|
post_callback_called = 0
|
|
128
150
|
options = {
|
|
129
|
-
webhook_url:
|
|
130
|
-
username:
|
|
131
|
-
custom_hook:
|
|
132
|
-
:
|
|
133
|
-
(message_opts[:attachments] = []) << { text:
|
|
151
|
+
webhook_url: 'http://slack.webhook.url',
|
|
152
|
+
username: 'test',
|
|
153
|
+
custom_hook: 'hook',
|
|
154
|
+
pre_callback: proc { |_opts, _notifier, backtrace, _message, message_opts|
|
|
155
|
+
(message_opts[:attachments] = []) << { text: backtrace.join("\n").to_s, color: 'danger' }
|
|
134
156
|
},
|
|
135
|
-
:
|
|
157
|
+
post_callback: proc { |_opts, _notifier, _backtrace, _message, _message_opts|
|
|
136
158
|
post_callback_called = 1
|
|
137
159
|
},
|
|
138
160
|
additional_parameters: {
|
|
139
|
-
icon_url:
|
|
161
|
+
icon_url: 'icon'
|
|
140
162
|
}
|
|
141
163
|
}
|
|
142
164
|
|
|
143
165
|
Slack::Notifier.any_instance.expects(:ping).with('',
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
]
|
|
166
|
+
icon_url: 'icon',
|
|
167
|
+
attachments: [{
|
|
168
|
+
text: fake_backtrace.join("\n"),
|
|
169
|
+
color: 'danger'
|
|
170
|
+
}])
|
|
149
171
|
|
|
150
172
|
slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
|
|
151
173
|
slack_notifier.call(@exception)
|
|
@@ -155,11 +177,9 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
155
177
|
private
|
|
156
178
|
|
|
157
179
|
def fake_exception
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
e
|
|
162
|
-
end
|
|
180
|
+
5 / 0
|
|
181
|
+
rescue StandardError => e
|
|
182
|
+
e
|
|
163
183
|
end
|
|
164
184
|
|
|
165
185
|
def fake_exception_without_backtrace
|
|
@@ -168,17 +188,19 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
168
188
|
|
|
169
189
|
def fake_backtrace
|
|
170
190
|
[
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
"backtrace line 3",
|
|
174
|
-
"backtrace line 4",
|
|
175
|
-
"backtrace line 5",
|
|
176
|
-
"backtrace line 6",
|
|
191
|
+
'backtrace line 1', 'backtrace line 2', 'backtrace line 3',
|
|
192
|
+
'backtrace line 4', 'backtrace line 5', 'backtrace line 6'
|
|
177
193
|
]
|
|
178
194
|
end
|
|
179
195
|
|
|
180
|
-
def
|
|
181
|
-
|
|
196
|
+
def fake_cleaned_backtrace
|
|
197
|
+
fake_backtrace[2..-1]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def fake_notification(exception = @exception, notification_options = {},
|
|
201
|
+
data_string = nil, expected_backtrace_lines = 10, additional_fields = [])
|
|
202
|
+
|
|
203
|
+
exception_name = "*#{exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A'}* `#{exception.class}`"
|
|
182
204
|
if notification_options[:env].nil?
|
|
183
205
|
text = "#{exception_name} *occured in background*"
|
|
184
206
|
else
|
|
@@ -193,15 +215,15 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
193
215
|
|
|
194
216
|
text += "\n"
|
|
195
217
|
|
|
196
|
-
fields = [
|
|
197
|
-
fields.push(
|
|
218
|
+
fields = [{ title: 'Exception', value: exception.message }]
|
|
219
|
+
fields.push(title: 'Hostname', value: 'example.com')
|
|
198
220
|
if exception.backtrace
|
|
199
|
-
formatted_backtrace =
|
|
200
|
-
fields.push(
|
|
221
|
+
formatted_backtrace = "```#{fake_cleaned_backtrace.first(expected_backtrace_lines).join("\n")}```"
|
|
222
|
+
fields.push(title: 'Backtrace', value: formatted_backtrace)
|
|
201
223
|
end
|
|
202
|
-
fields.push(
|
|
224
|
+
fields.push(title: 'Data', value: "```#{data_string}```") if data_string
|
|
225
|
+
additional_fields.each { |f| fields.push(f) }
|
|
203
226
|
|
|
204
|
-
{ attachments: [
|
|
227
|
+
{ attachments: [color: 'danger', text: text, fields: fields, mrkdwn_in: %w[text fields]] }
|
|
205
228
|
end
|
|
206
|
-
|
|
207
229
|
end
|