exception_notification 4.2.0 → 4.4.1
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 +106 -789
- 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 +34 -27
- 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 +75 -32
- 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 +19 -16
- data/lib/generators/exception_notification/install_generator.rb +11 -5
- data/lib/generators/exception_notification/templates/{exception_notification.rb → exception_notification.rb.erb} +14 -12
- 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 +66 -39
- data/test/exception_notifier/datadog_notifier_test.rb +153 -0
- data/test/exception_notifier/email_notifier_test.rb +301 -145
- data/test/exception_notifier/google_chat_notifier_test.rb +185 -0
- data/test/exception_notifier/hipchat_notifier_test.rb +112 -65
- 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 -6
- data/test/exception_notifier/slack_notifier_test.rb +109 -59
- 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 +68 -38
- 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 -38
- 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,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'aws-sdk-sns'
|
|
5
|
+
|
|
6
|
+
class SnsNotifierTest < ActiveSupport::TestCase
|
|
7
|
+
def setup
|
|
8
|
+
@exception = fake_exception
|
|
9
|
+
@exception.stubs(:class).returns('MyException')
|
|
10
|
+
@exception.stubs(:backtrace).returns(fake_backtrace)
|
|
11
|
+
@exception.stubs(:message).returns("undefined method 'method=' for Empty")
|
|
12
|
+
@options = {
|
|
13
|
+
access_key_id: 'my-access_key_id',
|
|
14
|
+
secret_access_key: 'my-secret_access_key',
|
|
15
|
+
region: 'us-east',
|
|
16
|
+
topic_arn: 'topicARN',
|
|
17
|
+
sns_prefix: '[App Exception]'
|
|
18
|
+
}
|
|
19
|
+
Socket.stubs(:gethostname).returns('example.com')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# initialize
|
|
23
|
+
|
|
24
|
+
test 'should initialize aws notifier with received params' do
|
|
25
|
+
Aws::SNS::Client.expects(:new).with(
|
|
26
|
+
region: 'us-east',
|
|
27
|
+
access_key_id: 'my-access_key_id',
|
|
28
|
+
secret_access_key: 'my-secret_access_key'
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
ExceptionNotifier::SnsNotifier.new(@options)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
test 'should raise an exception if region is not received' do
|
|
35
|
+
@options[:region] = nil
|
|
36
|
+
|
|
37
|
+
error = assert_raises ArgumentError do
|
|
38
|
+
ExceptionNotifier::SnsNotifier.new(@options)
|
|
39
|
+
end
|
|
40
|
+
assert_equal "You must provide 'region' option", error.message
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
test 'should raise an exception on publish if access_key_id is not received' do
|
|
44
|
+
@options[:access_key_id] = nil
|
|
45
|
+
error = assert_raises ArgumentError do
|
|
46
|
+
ExceptionNotifier::SnsNotifier.new(@options)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
assert_equal "You must provide 'access_key_id' option", error.message
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
test 'should raise an exception on publish if secret_access_key is not received' do
|
|
53
|
+
@options[:secret_access_key] = nil
|
|
54
|
+
error = assert_raises ArgumentError do
|
|
55
|
+
ExceptionNotifier::SnsNotifier.new(@options)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
assert_equal "You must provide 'secret_access_key' option", error.message
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# call
|
|
62
|
+
|
|
63
|
+
test 'should send a sns notification in background' do
|
|
64
|
+
Aws::SNS::Client.any_instance.expects(:publish).with(
|
|
65
|
+
topic_arn: 'topicARN',
|
|
66
|
+
message: "3 MyException occured in background\n" \
|
|
67
|
+
"Exception: undefined method 'method=' for Empty\n" \
|
|
68
|
+
"Hostname: example.com\n" \
|
|
69
|
+
"Backtrace:\n#{fake_backtrace.join("\n")}\n",
|
|
70
|
+
subject: '[App Exception] - 3 MyException occurred'
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
sns_notifier = ExceptionNotifier::SnsNotifier.new(@options)
|
|
74
|
+
sns_notifier.call(@exception, accumulated_errors_count: 3)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
test 'should send a sns notification with controller#action information' do
|
|
78
|
+
controller = mock('controller')
|
|
79
|
+
controller.stubs(:action_name).returns('index')
|
|
80
|
+
controller.stubs(:controller_name).returns('examples')
|
|
81
|
+
|
|
82
|
+
Aws::SNS::Client.any_instance.expects(:publish).with(
|
|
83
|
+
topic_arn: 'topicARN',
|
|
84
|
+
message: 'A MyException occurred while GET </examples> ' \
|
|
85
|
+
"was processed by examples#index\n" \
|
|
86
|
+
"Exception: undefined method 'method=' for Empty\n" \
|
|
87
|
+
"Hostname: example.com\n" \
|
|
88
|
+
"Backtrace:\n#{fake_backtrace.join("\n")}\n",
|
|
89
|
+
subject: '[App Exception] - A MyException occurred'
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
sns_notifier = ExceptionNotifier::SnsNotifier.new(@options)
|
|
93
|
+
sns_notifier.call(@exception,
|
|
94
|
+
env: {
|
|
95
|
+
'REQUEST_METHOD' => 'GET',
|
|
96
|
+
'REQUEST_URI' => '/examples',
|
|
97
|
+
'action_controller.instance' => controller
|
|
98
|
+
})
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def fake_exception
|
|
104
|
+
1 / 0
|
|
105
|
+
rescue StandardError => e
|
|
106
|
+
e
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def fake_exception_without_backtrace
|
|
110
|
+
StandardError.new('my custom error')
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def fake_backtrace
|
|
114
|
+
[
|
|
115
|
+
'backtrace line 1',
|
|
116
|
+
'backtrace line 2',
|
|
117
|
+
'backtrace line 3',
|
|
118
|
+
'backtrace line 4',
|
|
119
|
+
'backtrace line 5',
|
|
120
|
+
'backtrace line 6'
|
|
121
|
+
]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'httparty'
|
|
5
|
+
|
|
6
|
+
class TeamsNotifierTest < ActiveSupport::TestCase
|
|
7
|
+
test 'should send notification if properly configured' do
|
|
8
|
+
options = {
|
|
9
|
+
webhook_url: 'http://localhost:8000'
|
|
10
|
+
}
|
|
11
|
+
teams_notifier = ExceptionNotifier::TeamsNotifier.new
|
|
12
|
+
teams_notifier.httparty = FakeHTTParty.new
|
|
13
|
+
|
|
14
|
+
options = teams_notifier.call ArgumentError.new('foo'), options
|
|
15
|
+
|
|
16
|
+
body = ActiveSupport::JSON.decode options[:body]
|
|
17
|
+
assert body.key? 'title'
|
|
18
|
+
assert body.key? 'sections'
|
|
19
|
+
|
|
20
|
+
sections = body['sections']
|
|
21
|
+
header = sections[0]
|
|
22
|
+
|
|
23
|
+
assert_equal 2, sections.size
|
|
24
|
+
assert_equal 'A *ArgumentError* occurred.', header['activityTitle']
|
|
25
|
+
assert_equal 'foo', header['activitySubtitle']
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
test 'should send notification with create gitlab issue link if specified' do
|
|
29
|
+
options = {
|
|
30
|
+
webhook_url: 'http://localhost:8000',
|
|
31
|
+
git_url: 'github.com/aschen'
|
|
32
|
+
}
|
|
33
|
+
teams_notifier = ExceptionNotifier::TeamsNotifier.new
|
|
34
|
+
teams_notifier.httparty = FakeHTTParty.new
|
|
35
|
+
|
|
36
|
+
options = teams_notifier.call ArgumentError.new('foo'), options
|
|
37
|
+
|
|
38
|
+
body = ActiveSupport::JSON.decode options[:body]
|
|
39
|
+
|
|
40
|
+
potential_action = body['potentialAction']
|
|
41
|
+
assert_equal 2, potential_action.size
|
|
42
|
+
assert_equal '🦊 View in GitLab', potential_action[0]['name']
|
|
43
|
+
assert_equal '🦊 Create Issue in GitLab', potential_action[1]['name']
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
test 'should add other HTTParty options to params' do
|
|
47
|
+
options = {
|
|
48
|
+
webhook_url: 'http://localhost:8000',
|
|
49
|
+
username: 'Test Bot',
|
|
50
|
+
avatar: 'http://site.com/icon.png',
|
|
51
|
+
basic_auth: {
|
|
52
|
+
username: 'clara',
|
|
53
|
+
password: 'password'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
teams_notifier = ExceptionNotifier::TeamsNotifier.new
|
|
57
|
+
teams_notifier.httparty = FakeHTTParty.new
|
|
58
|
+
|
|
59
|
+
options = teams_notifier.call ArgumentError.new('foo'), options
|
|
60
|
+
|
|
61
|
+
assert options.key? :basic_auth
|
|
62
|
+
assert 'clara', options[:basic_auth][:username]
|
|
63
|
+
assert 'password', options[:basic_auth][:password]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
test "should use 'A' for exceptions count if :accumulated_errors_count option is nil" do
|
|
67
|
+
teams_notifier = ExceptionNotifier::TeamsNotifier.new
|
|
68
|
+
exception = ArgumentError.new('foo')
|
|
69
|
+
teams_notifier.instance_variable_set(:@exception, exception)
|
|
70
|
+
teams_notifier.instance_variable_set(:@options, {})
|
|
71
|
+
|
|
72
|
+
message_text = teams_notifier.send(:message_text)
|
|
73
|
+
header = message_text['sections'][0]
|
|
74
|
+
assert_equal 'A *ArgumentError* occurred.', header['activityTitle']
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
test 'should use direct errors count if :accumulated_errors_count option is 5' do
|
|
78
|
+
teams_notifier = ExceptionNotifier::TeamsNotifier.new
|
|
79
|
+
exception = ArgumentError.new('foo')
|
|
80
|
+
teams_notifier.instance_variable_set(:@exception, exception)
|
|
81
|
+
teams_notifier.instance_variable_set(:@options, accumulated_errors_count: 5)
|
|
82
|
+
message_text = teams_notifier.send(:message_text)
|
|
83
|
+
header = message_text['sections'][0]
|
|
84
|
+
assert_equal '5 *ArgumentError* occurred.', header['activityTitle']
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class FakeHTTParty
|
|
89
|
+
def post(_url, options)
|
|
90
|
+
options
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -1,33 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'test_helper'
|
|
2
4
|
require 'httparty'
|
|
3
5
|
|
|
4
6
|
class WebhookNotifierTest < ActiveSupport::TestCase
|
|
5
|
-
|
|
6
|
-
test "should send webhook notification if properly configured" do
|
|
7
|
+
test 'should send webhook notification if properly configured' do
|
|
7
8
|
ExceptionNotifier::WebhookNotifier.stubs(:new).returns(Object.new)
|
|
8
|
-
webhook = ExceptionNotifier::WebhookNotifier.new(
|
|
9
|
+
webhook = ExceptionNotifier::WebhookNotifier.new(url: 'http://localhost:8000')
|
|
9
10
|
webhook.stubs(:call).returns(fake_response)
|
|
10
11
|
response = webhook.call(fake_exception)
|
|
11
12
|
|
|
12
13
|
refute_nil response
|
|
13
14
|
assert_equal response[:status], 200
|
|
14
|
-
assert_equal response[:body][:exception][:error_class],
|
|
15
|
-
assert_includes response[:body][:exception][:message],
|
|
16
|
-
assert_includes response[:body][:exception][:backtrace],
|
|
17
|
-
|
|
18
|
-
assert response[:body][:request][:cookies].
|
|
19
|
-
assert_equal response[:body][:request][:url],
|
|
20
|
-
assert_equal response[:body][:request][:ip_address],
|
|
21
|
-
assert response[:body][:request][:environment].
|
|
22
|
-
assert_equal response[:body][:request][:controller],
|
|
23
|
-
assert response[:body][:request][:session].
|
|
24
|
-
assert response[:body][:request][:parameters].
|
|
25
|
-
assert response[:body][:data][:extra_data].
|
|
15
|
+
assert_equal response[:body][:exception][:error_class], 'ZeroDivisionError'
|
|
16
|
+
assert_includes response[:body][:exception][:message], 'divided by 0'
|
|
17
|
+
assert_includes response[:body][:exception][:backtrace], '/exception_notification/test/webhook_notifier_test.rb:48'
|
|
18
|
+
|
|
19
|
+
assert response[:body][:request][:cookies].key?(:cookie_item1)
|
|
20
|
+
assert_equal response[:body][:request][:url], 'http://example.com/example'
|
|
21
|
+
assert_equal response[:body][:request][:ip_address], '192.168.1.1'
|
|
22
|
+
assert response[:body][:request][:environment].key?(:env_item1)
|
|
23
|
+
assert_equal response[:body][:request][:controller], '#<ControllerName:0x007f9642a04d00>'
|
|
24
|
+
assert response[:body][:request][:session].key?(:session_item1)
|
|
25
|
+
assert response[:body][:request][:parameters].key?(:controller)
|
|
26
|
+
assert response[:body][:data][:extra_data].key?(:data_item1)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
test 'should send webhook notification with correct params data' do
|
|
30
|
+
url = 'http://localhost:8000'
|
|
31
|
+
fake_exception.stubs(:backtrace).returns('the backtrace')
|
|
32
|
+
webhook = ExceptionNotifier::WebhookNotifier.new(url: url)
|
|
33
|
+
|
|
34
|
+
HTTParty.expects(:send).with(:post, url, fake_params)
|
|
35
|
+
|
|
36
|
+
webhook.call(fake_exception)
|
|
26
37
|
end
|
|
27
38
|
|
|
28
|
-
test
|
|
39
|
+
test 'should call pre/post_callback if specified' do
|
|
29
40
|
HTTParty.stubs(:send).returns(fake_response)
|
|
30
|
-
webhook = ExceptionNotifier::WebhookNotifier.new(
|
|
41
|
+
webhook = ExceptionNotifier::WebhookNotifier.new(url: 'http://localhost:8000')
|
|
31
42
|
webhook.call(fake_exception)
|
|
32
43
|
end
|
|
33
44
|
|
|
@@ -35,34 +46,53 @@ class WebhookNotifierTest < ActiveSupport::TestCase
|
|
|
35
46
|
|
|
36
47
|
def fake_response
|
|
37
48
|
{
|
|
38
|
-
:
|
|
39
|
-
:
|
|
40
|
-
:
|
|
41
|
-
:
|
|
42
|
-
:
|
|
43
|
-
:
|
|
49
|
+
status: 200,
|
|
50
|
+
body: {
|
|
51
|
+
exception: {
|
|
52
|
+
error_class: 'ZeroDivisionError',
|
|
53
|
+
message: 'divided by 0',
|
|
54
|
+
backtrace: '/exception_notification/test/webhook_notifier_test.rb:48:in `/'
|
|
44
55
|
},
|
|
45
|
-
:
|
|
46
|
-
:
|
|
56
|
+
data: {
|
|
57
|
+
extra_data: { data_item1: 'datavalue1', data_item2: 'datavalue2' }
|
|
47
58
|
},
|
|
48
|
-
:
|
|
49
|
-
:
|
|
50
|
-
:
|
|
51
|
-
:
|
|
52
|
-
:
|
|
53
|
-
:
|
|
54
|
-
:
|
|
55
|
-
:
|
|
59
|
+
request: {
|
|
60
|
+
cookies: { cookie_item1: 'cookieitemvalue1', cookie_item2: 'cookieitemvalue2' },
|
|
61
|
+
url: 'http://example.com/example',
|
|
62
|
+
ip_address: '192.168.1.1',
|
|
63
|
+
environment: { env_item1: 'envitem1', env_item2: 'envitem2' },
|
|
64
|
+
controller: '#<ControllerName:0x007f9642a04d00>',
|
|
65
|
+
session: { session_item1: 'sessionitem1', session_item2: 'sessionitem2' },
|
|
66
|
+
parameters: { action: 'index', controller: 'projects' }
|
|
56
67
|
}
|
|
57
68
|
}
|
|
58
69
|
}
|
|
59
70
|
end
|
|
60
71
|
|
|
72
|
+
def fake_params
|
|
73
|
+
params = {
|
|
74
|
+
body: {
|
|
75
|
+
server: Socket.gethostname,
|
|
76
|
+
process: $PROCESS_ID,
|
|
77
|
+
exception: {
|
|
78
|
+
error_class: 'ZeroDivisionError',
|
|
79
|
+
message: 'divided by 0'.inspect,
|
|
80
|
+
backtrace: 'the backtrace'
|
|
81
|
+
},
|
|
82
|
+
data: {}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
params[:body][:rails_root] = Rails.root if defined?(::Rails) && Rails.respond_to?(:root)
|
|
87
|
+
|
|
88
|
+
params
|
|
89
|
+
end
|
|
90
|
+
|
|
61
91
|
def fake_exception
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
92
|
+
@fake_exception ||= begin
|
|
93
|
+
5 / 0
|
|
94
|
+
rescue StandardError => e
|
|
95
|
+
e
|
|
96
|
+
end
|
|
67
97
|
end
|
|
68
98
|
end
|
|
@@ -1,29 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'test_helper'
|
|
2
4
|
|
|
5
|
+
class ExceptionOne < StandardError; end
|
|
6
|
+
class ExceptionTwo < StandardError; end
|
|
7
|
+
|
|
3
8
|
class ExceptionNotifierTest < ActiveSupport::TestCase
|
|
4
|
-
|
|
9
|
+
setup do
|
|
10
|
+
ExceptionNotifier.register_exception_notifier(:email, exception_recipients: %w[dummyexceptions@example.com])
|
|
11
|
+
|
|
12
|
+
@notifier_calls = 0
|
|
13
|
+
@test_notifier = ->(_exception, _options) { @notifier_calls += 1 }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
teardown do
|
|
17
|
+
ExceptionNotifier.reset_notifiers!
|
|
18
|
+
|
|
19
|
+
Rails.cache.clear if defined?(Rails) && Rails.respond_to?(:cache)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
test 'should have default ignored exceptions' do
|
|
5
23
|
assert_equal ExceptionNotifier.ignored_exceptions,
|
|
6
|
-
|
|
7
|
-
|
|
24
|
+
['ActiveRecord::RecordNotFound', 'Mongoid::Errors::DocumentNotFound',
|
|
25
|
+
'AbstractController::ActionNotFound', 'ActionController::RoutingError',
|
|
26
|
+
'ActionController::UnknownFormat', 'ActionController::UrlGenerationError']
|
|
8
27
|
end
|
|
9
28
|
|
|
10
|
-
test
|
|
29
|
+
test 'should have email notifier registered' do
|
|
11
30
|
assert_equal ExceptionNotifier.notifiers, [:email]
|
|
12
31
|
end
|
|
13
32
|
|
|
14
|
-
test
|
|
33
|
+
test 'should have a valid email notifier' do
|
|
15
34
|
@email_notifier = ExceptionNotifier.registered_exception_notifier(:email)
|
|
16
35
|
refute_nil @email_notifier
|
|
17
36
|
assert_equal @email_notifier.class, ExceptionNotifier::EmailNotifier
|
|
18
37
|
assert_respond_to @email_notifier, :call
|
|
19
38
|
end
|
|
20
39
|
|
|
21
|
-
test
|
|
40
|
+
test 'should allow register/unregister another notifier' do
|
|
22
41
|
called = false
|
|
23
|
-
proc_notifier =
|
|
42
|
+
proc_notifier = ->(_exception, _options) { called = true }
|
|
24
43
|
ExceptionNotifier.register_exception_notifier(:proc, proc_notifier)
|
|
25
44
|
|
|
26
|
-
assert_equal ExceptionNotifier.notifiers.sort, [
|
|
45
|
+
assert_equal ExceptionNotifier.notifiers.sort, %i[email proc]
|
|
27
46
|
|
|
28
47
|
exception = StandardError.new
|
|
29
48
|
|
|
@@ -34,27 +53,27 @@ class ExceptionNotifierTest < ActiveSupport::TestCase
|
|
|
34
53
|
assert_equal ExceptionNotifier.notifiers, [:email]
|
|
35
54
|
end
|
|
36
55
|
|
|
37
|
-
test
|
|
56
|
+
test 'should allow select notifiers to send error to' do
|
|
38
57
|
notifier1_calls = 0
|
|
39
|
-
notifier1 =
|
|
58
|
+
notifier1 = ->(_exception, _options) { notifier1_calls += 1 }
|
|
40
59
|
ExceptionNotifier.register_exception_notifier(:notifier1, notifier1)
|
|
41
60
|
|
|
42
61
|
notifier2_calls = 0
|
|
43
|
-
notifier2 =
|
|
62
|
+
notifier2 = ->(_exception, _options) { notifier2_calls += 1 }
|
|
44
63
|
ExceptionNotifier.register_exception_notifier(:notifier2, notifier2)
|
|
45
64
|
|
|
46
|
-
assert_equal ExceptionNotifier.notifiers.sort, [
|
|
65
|
+
assert_equal ExceptionNotifier.notifiers.sort, %i[email notifier1 notifier2]
|
|
47
66
|
|
|
48
67
|
exception = StandardError.new
|
|
49
68
|
ExceptionNotifier.notify_exception(exception)
|
|
50
69
|
assert_equal notifier1_calls, 1
|
|
51
70
|
assert_equal notifier2_calls, 1
|
|
52
71
|
|
|
53
|
-
ExceptionNotifier.notify_exception(exception,
|
|
72
|
+
ExceptionNotifier.notify_exception(exception, notifiers: :notifier1)
|
|
54
73
|
assert_equal notifier1_calls, 2
|
|
55
74
|
assert_equal notifier2_calls, 1
|
|
56
75
|
|
|
57
|
-
ExceptionNotifier.notify_exception(exception,
|
|
76
|
+
ExceptionNotifier.notify_exception(exception, notifiers: :notifier2)
|
|
58
77
|
assert_equal notifier1_calls, 2
|
|
59
78
|
assert_equal notifier2_calls, 2
|
|
60
79
|
|
|
@@ -63,43 +82,207 @@ class ExceptionNotifierTest < ActiveSupport::TestCase
|
|
|
63
82
|
assert_equal ExceptionNotifier.notifiers, [:email]
|
|
64
83
|
end
|
|
65
84
|
|
|
66
|
-
test
|
|
67
|
-
env =
|
|
68
|
-
ExceptionNotifier.ignore_if do |
|
|
69
|
-
env !=
|
|
85
|
+
test 'should ignore exception if satisfies conditional ignore' do
|
|
86
|
+
env = 'production'
|
|
87
|
+
ExceptionNotifier.ignore_if do |_exception, _options|
|
|
88
|
+
env != 'production'
|
|
70
89
|
end
|
|
71
90
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
91
|
+
ExceptionNotifier.register_exception_notifier(:test, @test_notifier)
|
|
92
|
+
|
|
93
|
+
exception = StandardError.new
|
|
94
|
+
|
|
95
|
+
ExceptionNotifier.notify_exception(exception, notifiers: :test)
|
|
96
|
+
assert_equal @notifier_calls, 1
|
|
97
|
+
|
|
98
|
+
env = 'development'
|
|
99
|
+
ExceptionNotifier.notify_exception(exception, notifiers: :test)
|
|
100
|
+
assert_equal @notifier_calls, 1
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
test 'should ignore exception if satisfies by-notifier conditional ignore' do
|
|
104
|
+
notifier1_calls = 0
|
|
105
|
+
notifier1 = ->(_exception, _options) { notifier1_calls += 1 }
|
|
106
|
+
ExceptionNotifier.register_exception_notifier(:notifier1, notifier1)
|
|
107
|
+
|
|
108
|
+
notifier2_calls = 0
|
|
109
|
+
notifier2 = ->(_exception, _options) { notifier2_calls += 1 }
|
|
110
|
+
ExceptionNotifier.register_exception_notifier(:notifier2, notifier2)
|
|
111
|
+
|
|
112
|
+
env = 'production'
|
|
113
|
+
ExceptionNotifier.ignore_notifier_if(:notifier1) do |_exception, _options|
|
|
114
|
+
env == 'development'
|
|
115
|
+
end
|
|
116
|
+
ExceptionNotifier.ignore_notifier_if(:notifier2) do |_exception, _options|
|
|
117
|
+
env == 'production'
|
|
118
|
+
end
|
|
75
119
|
|
|
76
120
|
exception = StandardError.new
|
|
77
121
|
|
|
78
|
-
ExceptionNotifier.notify_exception(exception
|
|
79
|
-
assert_equal
|
|
122
|
+
ExceptionNotifier.notify_exception(exception)
|
|
123
|
+
assert_equal notifier1_calls, 1
|
|
124
|
+
assert_equal notifier2_calls, 0
|
|
125
|
+
|
|
126
|
+
env = 'development'
|
|
127
|
+
|
|
128
|
+
ExceptionNotifier.notify_exception(exception)
|
|
129
|
+
assert_equal notifier1_calls, 1
|
|
130
|
+
assert_equal notifier2_calls, 1
|
|
131
|
+
|
|
132
|
+
env = 'test'
|
|
133
|
+
|
|
134
|
+
ExceptionNotifier.notify_exception(exception)
|
|
135
|
+
assert_equal notifier1_calls, 2
|
|
136
|
+
assert_equal notifier2_calls, 2
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
test 'should return false if all the registered notifiers are ignored' do
|
|
140
|
+
ExceptionNotifier.notifiers.each do |notifier|
|
|
141
|
+
# make sure to register no other notifiers but the tested ones
|
|
142
|
+
ExceptionNotifier.unregister_exception_notifier(notifier)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
ExceptionNotifier.register_exception_notifier(:notifier1, ->(_, _) {})
|
|
146
|
+
ExceptionNotifier.register_exception_notifier(:notifier2, ->(_, _) {})
|
|
147
|
+
|
|
148
|
+
ExceptionNotifier.ignore_notifier_if(:notifier1) do |exception, _options|
|
|
149
|
+
exception.message =~ /non_critical_error/
|
|
150
|
+
end
|
|
151
|
+
ExceptionNotifier.ignore_notifier_if(:notifier2) do |exception, _options|
|
|
152
|
+
exception.message =~ /non_critical_error/
|
|
153
|
+
end
|
|
80
154
|
|
|
81
|
-
|
|
82
|
-
ExceptionNotifier.notify_exception(exception, {:notifiers => :test})
|
|
83
|
-
assert_equal notifier_calls, 1
|
|
155
|
+
exception = StandardError.new('a non_critical_error occured.')
|
|
84
156
|
|
|
85
|
-
ExceptionNotifier.
|
|
86
|
-
ExceptionNotifier.unregister_exception_notifier(:test)
|
|
157
|
+
refute ExceptionNotifier.notify_exception(exception)
|
|
87
158
|
end
|
|
88
159
|
|
|
89
|
-
test
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
160
|
+
test 'should return true if one of the notifiers fires' do
|
|
161
|
+
ExceptionNotifier.notifiers.each do |notifier|
|
|
162
|
+
# make sure to register no other notifiers but the tested ones
|
|
163
|
+
ExceptionNotifier.unregister_exception_notifier(notifier)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
ExceptionNotifier.register_exception_notifier(:notifier1, ->(_, _) {})
|
|
167
|
+
ExceptionNotifier.register_exception_notifier(:notifier2, ->(_, _) {})
|
|
168
|
+
|
|
169
|
+
ExceptionNotifier.ignore_notifier_if(:notifier1) do |exception, _options|
|
|
170
|
+
exception.message =~ /non-critical\serror/
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
exception = StandardError.new('a non-critical error occured')
|
|
174
|
+
|
|
175
|
+
assert ExceptionNotifier.notify_exception(exception)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
test 'should not send notification if one of ignored exceptions' do
|
|
179
|
+
ExceptionNotifier.register_exception_notifier(:test, @test_notifier)
|
|
180
|
+
|
|
181
|
+
exception = StandardError.new
|
|
182
|
+
|
|
183
|
+
ExceptionNotifier.notify_exception(exception, notifiers: :test)
|
|
184
|
+
assert_equal @notifier_calls, 1
|
|
185
|
+
|
|
186
|
+
ExceptionNotifier.notify_exception(exception, notifiers: :test, ignore_exceptions: 'StandardError')
|
|
187
|
+
assert_equal @notifier_calls, 1
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
test 'should not send notification if subclass of one of ignored exceptions' do
|
|
191
|
+
ExceptionNotifier.register_exception_notifier(:test, @test_notifier)
|
|
192
|
+
|
|
193
|
+
class StandardErrorSubclass < StandardError
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
exception = StandardErrorSubclass.new
|
|
197
|
+
|
|
198
|
+
ExceptionNotifier.notify_exception(exception, notifiers: :test)
|
|
199
|
+
assert_equal @notifier_calls, 1
|
|
200
|
+
|
|
201
|
+
ExceptionNotifier.notify_exception(exception, notifiers: :test, ignore_exceptions: 'StandardError')
|
|
202
|
+
assert_equal @notifier_calls, 1
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
test 'should not send notification if extended module one of ignored exceptions' do
|
|
206
|
+
ExceptionNotifier.register_exception_notifier(:test, @test_notifier)
|
|
207
|
+
|
|
208
|
+
module StandardErrorModule; end
|
|
93
209
|
|
|
94
210
|
exception = StandardError.new
|
|
211
|
+
exception.extend StandardErrorModule
|
|
212
|
+
|
|
213
|
+
ExceptionNotifier.notify_exception(exception, notifiers: :test)
|
|
214
|
+
assert_equal @notifier_calls, 1
|
|
215
|
+
|
|
216
|
+
ignore_exceptions = 'ExceptionNotifierTest::StandardErrorModule'
|
|
217
|
+
ExceptionNotifier.notify_exception(exception, notifiers: :test, ignore_exceptions: ignore_exceptions)
|
|
218
|
+
assert_equal @notifier_calls, 1
|
|
219
|
+
end
|
|
95
220
|
|
|
96
|
-
|
|
97
|
-
|
|
221
|
+
test 'should not send notification if prepended module at singleton class one of ignored exceptions' do
|
|
222
|
+
ExceptionNotifier.register_exception_notifier(:test, @test_notifier)
|
|
98
223
|
|
|
99
|
-
|
|
100
|
-
assert_equal notifier_calls, 1
|
|
224
|
+
module StandardErrorModule; end
|
|
101
225
|
|
|
102
|
-
|
|
226
|
+
exception = StandardError.new
|
|
227
|
+
exception.singleton_class.prepend StandardErrorModule
|
|
228
|
+
|
|
229
|
+
ExceptionNotifier.notify_exception(exception, notifiers: :test)
|
|
230
|
+
assert_equal @notifier_calls, 1
|
|
231
|
+
|
|
232
|
+
ignore_exceptions = 'ExceptionNotifierTest::StandardErrorModule'
|
|
233
|
+
ExceptionNotifier.notify_exception(exception, notifiers: :test, ignore_exceptions: ignore_exceptions)
|
|
234
|
+
assert_equal @notifier_calls, 1
|
|
103
235
|
end
|
|
104
236
|
|
|
237
|
+
test 'should call received block' do
|
|
238
|
+
@block_called = false
|
|
239
|
+
notifier = ->(_exception, _options, &block) { block.call }
|
|
240
|
+
ExceptionNotifier.register_exception_notifier(:test, notifier)
|
|
241
|
+
|
|
242
|
+
exception = ExceptionOne.new
|
|
243
|
+
|
|
244
|
+
ExceptionNotifier.notify_exception(exception) do
|
|
245
|
+
@block_called = true
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
assert @block_called
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
test 'should not call group_error! or send_notification? if error_grouping false' do
|
|
252
|
+
exception = StandardError.new
|
|
253
|
+
ExceptionNotifier.expects(:group_error!).never
|
|
254
|
+
ExceptionNotifier.expects(:send_notification?).never
|
|
255
|
+
|
|
256
|
+
ExceptionNotifier.notify_exception(exception)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
test 'should call group_error! and send_notification? if error_grouping true' do
|
|
260
|
+
ExceptionNotifier.error_grouping = true
|
|
261
|
+
|
|
262
|
+
exception = StandardError.new
|
|
263
|
+
ExceptionNotifier.expects(:group_error!).once
|
|
264
|
+
ExceptionNotifier.expects(:send_notification?).once
|
|
265
|
+
|
|
266
|
+
ExceptionNotifier.notify_exception(exception)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
test 'should skip notification if send_notification? is false' do
|
|
270
|
+
ExceptionNotifier.error_grouping = true
|
|
271
|
+
|
|
272
|
+
exception = StandardError.new
|
|
273
|
+
ExceptionNotifier.expects(:group_error!).once.returns(1)
|
|
274
|
+
ExceptionNotifier.expects(:send_notification?).with(exception, 1).once.returns(false)
|
|
275
|
+
|
|
276
|
+
refute ExceptionNotifier.notify_exception(exception)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
test 'should send notification if send_notification? is true' do
|
|
280
|
+
ExceptionNotifier.error_grouping = true
|
|
281
|
+
|
|
282
|
+
exception = StandardError.new
|
|
283
|
+
ExceptionNotifier.expects(:group_error!).once.returns(1)
|
|
284
|
+
ExceptionNotifier.expects(:send_notification?).with(exception, 1).once.returns(true)
|
|
285
|
+
|
|
286
|
+
assert ExceptionNotifier.notify_exception(exception)
|
|
287
|
+
end
|
|
105
288
|
end
|