exception_notification 4.3.0 → 4.5.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 +5 -5
- data/Appraisals +4 -2
- data/CHANGELOG.rdoc +47 -0
- data/CONTRIBUTING.md +18 -0
- data/Gemfile +3 -1
- data/README.md +97 -945
- 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 -24
- data/gemfiles/{rails4_0.gemfile → rails5_2.gemfile} +2 -2
- data/gemfiles/{rails4_1.gemfile → rails6_0.gemfile} +2 -2
- data/gemfiles/{rails4_2.gemfile → rails6_1.gemfile} +2 -2
- data/gemfiles/{rails5_0.gemfile → rails7_0.gemfile} +2 -2
- data/lib/exception_notification/rack.rb +28 -30
- data/lib/exception_notification/rails.rb +2 -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_notification.rb +3 -0
- data/lib/exception_notifier/base_notifier.rb +10 -5
- data/lib/exception_notifier/datadog_notifier.rb +156 -0
- data/lib/exception_notifier/email_notifier.rb +73 -88
- data/lib/exception_notifier/google_chat_notifier.rb +27 -119
- data/lib/exception_notifier/hipchat_notifier.rb +13 -12
- data/lib/exception_notifier/irc_notifier.rb +36 -33
- data/lib/exception_notifier/mattermost_notifier.rb +54 -137
- data/lib/exception_notifier/modules/backtrace_cleaner.rb +2 -2
- data/lib/exception_notifier/modules/error_grouping.rb +24 -13
- data/lib/exception_notifier/modules/formatter.rb +125 -0
- data/lib/exception_notifier/notifier.rb +9 -6
- data/lib/exception_notifier/slack_notifier.rb +65 -40
- data/lib/exception_notifier/sns_notifier.rb +23 -13
- data/lib/exception_notifier/teams_notifier.rb +67 -46
- 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/exception_notification.html.erb +2 -2
- data/lib/exception_notifier/views/exception_notifier/exception_notification.text.erb +2 -2
- data/lib/exception_notifier/webhook_notifier.rb +17 -14
- data/lib/exception_notifier.rb +65 -10
- 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 +75 -13
- data/test/exception_notification/resque_test.rb +54 -0
- data/test/exception_notifier/datadog_notifier_test.rb +153 -0
- data/test/exception_notifier/email_notifier_test.rb +275 -153
- data/test/exception_notifier/google_chat_notifier_test.rb +158 -101
- data/test/exception_notifier/hipchat_notifier_test.rb +84 -81
- data/test/exception_notifier/irc_notifier_test.rb +36 -34
- data/test/exception_notifier/mattermost_notifier_test.rb +213 -67
- data/test/exception_notifier/modules/error_grouping_test.rb +41 -40
- 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 +66 -63
- data/test/exception_notifier/sns_notifier_test.rb +84 -32
- data/test/exception_notifier/teams_notifier_test.rb +25 -26
- data/test/exception_notifier/webhook_notifier_test.rb +52 -48
- data/test/exception_notifier_test.rb +150 -41
- 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 +134 -175
- data/gemfiles/rails5_1.gemfile +0 -7
- data/lib/exception_notifier/campfire_notifier.rb +0 -40
- 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/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/config.ru +0 -4
- 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 -237
- data/test/dummy/test/test_helper.rb +0 -7
- data/test/exception_notifier/campfire_notifier_test.rb +0 -120
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'test_helper'
|
|
2
4
|
require 'carrier-pigeon'
|
|
3
5
|
|
|
4
6
|
class IrcNotifierTest < ActiveSupport::TestCase
|
|
5
|
-
|
|
6
|
-
test "should send irc notification if properly configured" do
|
|
7
|
+
test 'should send irc notification if properly configured' do
|
|
7
8
|
options = {
|
|
8
|
-
:
|
|
9
|
+
domain: 'irc.example.com'
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
CarrierPigeon.expects(:send).with(has_key(:uri)) do |v|
|
|
@@ -16,29 +17,30 @@ class IrcNotifierTest < ActiveSupport::TestCase
|
|
|
16
17
|
irc.call(fake_exception)
|
|
17
18
|
end
|
|
18
19
|
|
|
19
|
-
test
|
|
20
|
+
test 'should exclude errors count in message if :accumulated_errors_count nil' do
|
|
20
21
|
irc = ExceptionNotifier::IrcNotifier.new({})
|
|
21
22
|
irc.stubs(:active?).returns(true)
|
|
22
23
|
|
|
23
|
-
irc.expects(:send_message).with{ |message| message.include?(
|
|
24
|
+
irc.expects(:send_message).with { |message| message.include?('divided by 0') }.once
|
|
24
25
|
irc.call(fake_exception)
|
|
25
26
|
end
|
|
26
27
|
|
|
27
|
-
test
|
|
28
|
+
test 'should include errors count in message if :accumulated_errors_count is 3' do
|
|
28
29
|
irc = ExceptionNotifier::IrcNotifier.new({})
|
|
29
30
|
irc.stubs(:active?).returns(true)
|
|
30
31
|
|
|
31
|
-
irc.expects(:send_message).with{ |message| message.include?("(3 times)'divided by 0'") }.once
|
|
32
|
+
irc.expects(:send_message).with { |message| message.include?("(3 times)'divided by 0'") }.once
|
|
32
33
|
irc.call(fake_exception, accumulated_errors_count: 3)
|
|
33
34
|
end
|
|
34
35
|
|
|
35
|
-
test
|
|
36
|
-
pre_callback_called
|
|
36
|
+
test 'should call pre/post_callback if specified' do
|
|
37
|
+
pre_callback_called = 0
|
|
38
|
+
post_callback_called = 0
|
|
37
39
|
|
|
38
40
|
options = {
|
|
39
|
-
:
|
|
40
|
-
:
|
|
41
|
-
:
|
|
41
|
+
domain: 'irc.example.com',
|
|
42
|
+
pre_callback: proc { |*| pre_callback_called += 1 },
|
|
43
|
+
post_callback: proc { |*| post_callback_called += 1 }
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
CarrierPigeon.expects(:send).with(has_key(:uri)) do |v|
|
|
@@ -51,9 +53,9 @@ class IrcNotifierTest < ActiveSupport::TestCase
|
|
|
51
53
|
assert_equal(1, post_callback_called)
|
|
52
54
|
end
|
|
53
55
|
|
|
54
|
-
test
|
|
56
|
+
test 'should send irc notification without backtrace info if properly configured' do
|
|
55
57
|
options = {
|
|
56
|
-
:
|
|
58
|
+
domain: 'irc.example.com'
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
CarrierPigeon.expects(:send).with(has_key(:uri)) do |v|
|
|
@@ -64,25 +66,25 @@ class IrcNotifierTest < ActiveSupport::TestCase
|
|
|
64
66
|
irc.call(fake_exception_without_backtrace)
|
|
65
67
|
end
|
|
66
68
|
|
|
67
|
-
test
|
|
69
|
+
test 'should properly construct URI from constituent parts' do
|
|
68
70
|
options = {
|
|
69
|
-
:
|
|
70
|
-
:
|
|
71
|
-
:
|
|
72
|
-
:
|
|
73
|
-
:
|
|
71
|
+
nick: 'BadNewsBot',
|
|
72
|
+
password: 'secret',
|
|
73
|
+
domain: 'irc.example.com',
|
|
74
|
+
port: 9999,
|
|
75
|
+
channel: '#exceptions'
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
CarrierPigeon.expects(:send).with(has_entry(uri:
|
|
78
|
+
CarrierPigeon.expects(:send).with(has_entry(uri: 'irc://BadNewsBot:secret@irc.example.com:9999/#exceptions'))
|
|
77
79
|
|
|
78
80
|
irc = ExceptionNotifier::IrcNotifier.new(options)
|
|
79
81
|
irc.call(fake_exception)
|
|
80
82
|
end
|
|
81
83
|
|
|
82
|
-
test
|
|
84
|
+
test 'should properly add recipients if specified' do
|
|
83
85
|
options = {
|
|
84
86
|
domain: 'irc.example.com',
|
|
85
|
-
recipients: [
|
|
87
|
+
recipients: %w[peter michael samir]
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
CarrierPigeon.expects(:send).with(has_key(:uri)) do |v|
|
|
@@ -93,7 +95,7 @@ class IrcNotifierTest < ActiveSupport::TestCase
|
|
|
93
95
|
irc.call(fake_exception)
|
|
94
96
|
end
|
|
95
97
|
|
|
96
|
-
test
|
|
98
|
+
test 'should properly set miscellaneous options' do
|
|
97
99
|
options = {
|
|
98
100
|
domain: 'irc.example.com',
|
|
99
101
|
ssl: true,
|
|
@@ -102,11 +104,13 @@ class IrcNotifierTest < ActiveSupport::TestCase
|
|
|
102
104
|
prefix: '[test notification]'
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
|
|
107
|
+
entries = {
|
|
106
108
|
ssl: true,
|
|
107
109
|
join: true,
|
|
108
|
-
notice: true
|
|
109
|
-
|
|
110
|
+
notice: true
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
CarrierPigeon.expects(:send).with(has_entries(entries)) do |v|
|
|
110
114
|
/\[test notification\]/.match(v[:message])
|
|
111
115
|
end
|
|
112
116
|
|
|
@@ -114,8 +118,8 @@ class IrcNotifierTest < ActiveSupport::TestCase
|
|
|
114
118
|
irc.call(fake_exception)
|
|
115
119
|
end
|
|
116
120
|
|
|
117
|
-
test
|
|
118
|
-
wrong_params = { domain: '##scriptkiddie.com###'}
|
|
121
|
+
test 'should not send irc notification if badly configured' do
|
|
122
|
+
wrong_params = { domain: '##scriptkiddie.com###' }
|
|
119
123
|
irc = ExceptionNotifier::IrcNotifier.new(wrong_params)
|
|
120
124
|
|
|
121
125
|
assert_nil irc.call(fake_exception)
|
|
@@ -124,11 +128,9 @@ class IrcNotifierTest < ActiveSupport::TestCase
|
|
|
124
128
|
private
|
|
125
129
|
|
|
126
130
|
def fake_exception
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
e
|
|
131
|
-
end
|
|
131
|
+
5 / 0
|
|
132
|
+
rescue StandardError => e
|
|
133
|
+
e
|
|
132
134
|
end
|
|
133
135
|
|
|
134
136
|
def fake_exception_without_backtrace
|
|
@@ -1,105 +1,251 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'test_helper'
|
|
2
4
|
require 'httparty'
|
|
5
|
+
require 'timecop'
|
|
6
|
+
require 'json'
|
|
3
7
|
|
|
4
8
|
class MattermostNotifierTest < ActiveSupport::TestCase
|
|
9
|
+
URL = 'http://localhost:8000'
|
|
10
|
+
|
|
11
|
+
def setup
|
|
12
|
+
Timecop.freeze('2018-12-09 12:07:16 UTC')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def teardown
|
|
16
|
+
Timecop.return
|
|
17
|
+
end
|
|
5
18
|
|
|
6
|
-
test
|
|
7
|
-
|
|
8
|
-
:
|
|
19
|
+
test 'should send notification if properly configured' do
|
|
20
|
+
opts = {
|
|
21
|
+
body: default_body.to_json,
|
|
22
|
+
headers: default_headers
|
|
9
23
|
}
|
|
10
|
-
mattermost_notifier = ExceptionNotifier::MattermostNotifier.new
|
|
11
|
-
mattermost_notifier.httparty = FakeHTTParty.new
|
|
12
24
|
|
|
13
|
-
|
|
25
|
+
HTTParty.expects(:post).with(URL, opts)
|
|
26
|
+
notifier.call ArgumentError.new('foo')
|
|
27
|
+
end
|
|
14
28
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
test 'should send notification with create issue link if specified' do
|
|
30
|
+
body = default_body.merge(
|
|
31
|
+
text: [
|
|
32
|
+
'@channel',
|
|
33
|
+
error_occurred_in,
|
|
34
|
+
'An *ArgumentError* occurred.',
|
|
35
|
+
'*foo*',
|
|
36
|
+
github_link
|
|
37
|
+
].join("\n")
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
opts = {
|
|
41
|
+
body: body.to_json,
|
|
42
|
+
headers: default_headers
|
|
43
|
+
}
|
|
18
44
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
assert_equal '@channel', text[0]
|
|
22
|
-
assert_equal 'An *ArgumentError* occured.', text[2]
|
|
23
|
-
assert_equal '*foo*', text[3]
|
|
45
|
+
HTTParty.expects(:post).with(URL, opts)
|
|
46
|
+
notifier.call ArgumentError.new('foo'), git_url: 'github.com/aschen'
|
|
24
47
|
end
|
|
25
48
|
|
|
26
|
-
test
|
|
27
|
-
|
|
28
|
-
:
|
|
29
|
-
:
|
|
49
|
+
test 'should add username and icon_url params to the notification if specified' do
|
|
50
|
+
body = default_body.merge(
|
|
51
|
+
username: 'Test Bot',
|
|
52
|
+
icon_url: 'http://site.com/icon.png'
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
opts = {
|
|
56
|
+
body: body.to_json,
|
|
57
|
+
headers: default_headers
|
|
30
58
|
}
|
|
31
|
-
mattermost_notifier = ExceptionNotifier::MattermostNotifier.new
|
|
32
|
-
mattermost_notifier.httparty = FakeHTTParty.new
|
|
33
59
|
|
|
34
|
-
|
|
60
|
+
HTTParty.expects(:post).with(URL, opts)
|
|
61
|
+
notifier.call(
|
|
62
|
+
ArgumentError.new('foo'),
|
|
63
|
+
username: 'Test Bot',
|
|
64
|
+
avatar: 'http://site.com/icon.png'
|
|
65
|
+
)
|
|
66
|
+
end
|
|
35
67
|
|
|
36
|
-
|
|
68
|
+
test 'should add other HTTParty options to params' do
|
|
69
|
+
opts = {
|
|
70
|
+
basic_auth: {
|
|
71
|
+
username: 'clara',
|
|
72
|
+
password: 'password'
|
|
73
|
+
},
|
|
74
|
+
body: default_body.to_json,
|
|
75
|
+
headers: default_headers
|
|
76
|
+
}
|
|
37
77
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
78
|
+
HTTParty.expects(:post).with(URL, opts)
|
|
79
|
+
notifier.call(
|
|
80
|
+
ArgumentError.new('foo'),
|
|
81
|
+
basic_auth: {
|
|
82
|
+
username: 'clara',
|
|
83
|
+
password: 'password'
|
|
84
|
+
}
|
|
85
|
+
)
|
|
41
86
|
end
|
|
42
87
|
|
|
43
|
-
test
|
|
44
|
-
|
|
45
|
-
:
|
|
46
|
-
:
|
|
47
|
-
:avatar => 'http://site.com/icon.png'
|
|
88
|
+
test "should use 'An' for exceptions count if :accumulated_errors_count option is nil" do
|
|
89
|
+
opts = {
|
|
90
|
+
body: default_body.to_json,
|
|
91
|
+
headers: default_headers
|
|
48
92
|
}
|
|
49
|
-
mattermost_notifier = ExceptionNotifier::MattermostNotifier.new
|
|
50
|
-
mattermost_notifier.httparty = FakeHTTParty.new
|
|
51
93
|
|
|
52
|
-
|
|
94
|
+
HTTParty.expects(:post).with(URL, opts)
|
|
95
|
+
notifier.call(ArgumentError.new('foo'))
|
|
96
|
+
end
|
|
53
97
|
|
|
54
|
-
|
|
98
|
+
test 'shoud use direct errors count if :accumulated_errors_count option is 5' do
|
|
99
|
+
body = default_body.merge(
|
|
100
|
+
text: [
|
|
101
|
+
'@channel',
|
|
102
|
+
error_occurred_in,
|
|
103
|
+
'5 *ArgumentError* occurred.',
|
|
104
|
+
'*foo*'
|
|
105
|
+
].join("\n")
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
opts = {
|
|
109
|
+
body: body.to_json,
|
|
110
|
+
headers: default_headers
|
|
111
|
+
}
|
|
55
112
|
|
|
56
|
-
|
|
57
|
-
|
|
113
|
+
HTTParty.expects(:post).with(URL, opts)
|
|
114
|
+
notifier.call(ArgumentError.new('foo'), accumulated_errors_count: 5)
|
|
58
115
|
end
|
|
59
116
|
|
|
60
|
-
test 'should
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
117
|
+
test 'should include backtrace and request info' do
|
|
118
|
+
body = default_body.merge(text: [
|
|
119
|
+
'@channel',
|
|
120
|
+
error_occurred_in,
|
|
121
|
+
'An *ArgumentError* occurred.',
|
|
122
|
+
'*foo*',
|
|
123
|
+
request_info,
|
|
124
|
+
backtrace_info
|
|
125
|
+
].join("\n"))
|
|
126
|
+
|
|
127
|
+
opts = {
|
|
128
|
+
body: body.to_json,
|
|
129
|
+
headers: default_headers
|
|
69
130
|
}
|
|
70
|
-
mattermost_notifier = ExceptionNotifier::MattermostNotifier.new
|
|
71
|
-
mattermost_notifier.httparty = FakeHTTParty.new
|
|
72
131
|
|
|
73
|
-
|
|
132
|
+
HTTParty.expects(:post).with(URL, opts)
|
|
74
133
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
134
|
+
exception = ArgumentError.new('foo')
|
|
135
|
+
exception.set_backtrace([
|
|
136
|
+
"app/controllers/my_controller.rb:53:in `my_controller_params'",
|
|
137
|
+
"app/controllers/my_controller.rb:34:in `update'"
|
|
138
|
+
])
|
|
139
|
+
|
|
140
|
+
notifier.call(exception, env: test_env)
|
|
78
141
|
end
|
|
79
142
|
|
|
80
|
-
test
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
143
|
+
test 'should include exception_data_info' do
|
|
144
|
+
body = default_body.merge(
|
|
145
|
+
text: [
|
|
146
|
+
'@channel',
|
|
147
|
+
error_occurred_in,
|
|
148
|
+
'An *ArgumentError* occurred.',
|
|
149
|
+
'*foo*',
|
|
150
|
+
request_info,
|
|
151
|
+
exception_data_info
|
|
152
|
+
].join("\n")
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
opts = {
|
|
156
|
+
body: body.to_json,
|
|
157
|
+
headers: default_headers
|
|
158
|
+
}
|
|
85
159
|
|
|
86
|
-
|
|
160
|
+
env = test_env.merge(
|
|
161
|
+
'exception_notifier.exception_data' => { foo: 'bar', john: 'doe' }
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
HTTParty.expects(:post).with(URL, opts)
|
|
165
|
+
notifier.call(ArgumentError.new('foo'), env: env)
|
|
87
166
|
end
|
|
88
167
|
|
|
89
|
-
|
|
90
|
-
mattermost_notifier = ExceptionNotifier::MattermostNotifier.new
|
|
91
|
-
exception = ArgumentError.new("foo")
|
|
92
|
-
mattermost_notifier.instance_variable_set(:@exception, exception)
|
|
93
|
-
mattermost_notifier.instance_variable_set(:@options, { accumulated_errors_count: 5 })
|
|
168
|
+
private
|
|
94
169
|
|
|
95
|
-
|
|
170
|
+
def notifier
|
|
171
|
+
ExceptionNotifier::MattermostNotifier.new(webhook_url: URL)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def default_body
|
|
175
|
+
{
|
|
176
|
+
text: [
|
|
177
|
+
'@channel',
|
|
178
|
+
error_occurred_in,
|
|
179
|
+
'An *ArgumentError* occurred.',
|
|
180
|
+
'*foo*'
|
|
181
|
+
].join("\n"),
|
|
182
|
+
username: 'Exception Notifier'
|
|
183
|
+
}
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def default_headers
|
|
187
|
+
{ 'Content-Type' => 'application/json' }
|
|
96
188
|
end
|
|
97
|
-
end
|
|
98
189
|
|
|
99
|
-
|
|
190
|
+
def test_env
|
|
191
|
+
Rack::MockRequest.env_for(
|
|
192
|
+
'/',
|
|
193
|
+
'HTTP_HOST' => 'test.address',
|
|
194
|
+
'REMOTE_ADDR' => '127.0.0.1',
|
|
195
|
+
'HTTP_USER_AGENT' => 'Rails Testing',
|
|
196
|
+
params: { id: 'foo' }
|
|
197
|
+
)
|
|
198
|
+
end
|
|
100
199
|
|
|
101
|
-
def
|
|
102
|
-
|
|
200
|
+
def error_occurred_in
|
|
201
|
+
if defined?(::Rails) && ::Rails.respond_to?(:env)
|
|
202
|
+
'### ⚠️ Error occurred in test ⚠️'
|
|
203
|
+
else
|
|
204
|
+
'### ⚠️ Error occurred ⚠️'
|
|
205
|
+
end
|
|
103
206
|
end
|
|
104
207
|
|
|
208
|
+
def github_link
|
|
209
|
+
if defined?(::Rails) && ::Rails.respond_to?(:application)
|
|
210
|
+
'[Create an issue]' \
|
|
211
|
+
'(github.com/aschen/dummy/issues/new/?issue%5Btitle%5D=%5BBUG%5D+Error+500+%3A++%28ArgumentError%29+foo)'
|
|
212
|
+
else
|
|
213
|
+
# TODO: fix missing app name
|
|
214
|
+
'[Create an issue]' \
|
|
215
|
+
'(github.com/aschen//issues/new/?issue%5Btitle%5D=%5BBUG%5D+Error+500+%3A++%28ArgumentError%29+foo)'
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def request_info
|
|
220
|
+
[
|
|
221
|
+
'### Request',
|
|
222
|
+
'```',
|
|
223
|
+
'* url : http://test.address/?id=foo',
|
|
224
|
+
'* http_method : GET',
|
|
225
|
+
'* ip_address : 127.0.0.1',
|
|
226
|
+
'* parameters : {"id"=>"foo"}',
|
|
227
|
+
'* timestamp : 2018-12-09 12:07:16 UTC',
|
|
228
|
+
'```'
|
|
229
|
+
]
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def backtrace_info
|
|
233
|
+
[
|
|
234
|
+
'### Backtrace',
|
|
235
|
+
'```',
|
|
236
|
+
"* app/controllers/my_controller.rb:53:in `my_controller_params'",
|
|
237
|
+
"* app/controllers/my_controller.rb:34:in `update'",
|
|
238
|
+
'```'
|
|
239
|
+
]
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def exception_data_info
|
|
243
|
+
[
|
|
244
|
+
'### Data',
|
|
245
|
+
'```',
|
|
246
|
+
'* foo : bar',
|
|
247
|
+
'* john : doe',
|
|
248
|
+
'```'
|
|
249
|
+
]
|
|
250
|
+
end
|
|
105
251
|
end
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'test_helper'
|
|
2
4
|
|
|
3
5
|
class ErrorGroupTest < ActiveSupport::TestCase
|
|
4
|
-
|
|
5
6
|
setup do
|
|
6
7
|
module TestModule
|
|
7
8
|
include ExceptionNotifier::ErrorGrouping
|
|
8
|
-
@@error_grouping_cache = ActiveSupport::Cache::FileStore.new(
|
|
9
|
+
@@error_grouping_cache = ActiveSupport::Cache::FileStore.new('test/dummy/tmp/non_default_location')
|
|
9
10
|
end
|
|
10
11
|
|
|
11
|
-
@exception = RuntimeError.new(
|
|
12
|
-
@exception.stubs(:backtrace).returns([
|
|
12
|
+
@exception = RuntimeError.new('ERROR')
|
|
13
|
+
@exception.stubs(:backtrace).returns(['/path/where/error/raised:1'])
|
|
13
14
|
|
|
14
|
-
@exception2 = RuntimeError.new(
|
|
15
|
-
@exception2.stubs(:backtrace).returns([
|
|
15
|
+
@exception2 = RuntimeError.new('ERROR2')
|
|
16
|
+
@exception2.stubs(:backtrace).returns(['/path/where/error/found:2'])
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
teardown do
|
|
@@ -20,88 +21,88 @@ class ErrorGroupTest < ActiveSupport::TestCase
|
|
|
20
21
|
TestModule.fallback_cache_store.clear
|
|
21
22
|
end
|
|
22
23
|
|
|
23
|
-
test
|
|
24
|
+
test 'should add additional option: error_grouping' do
|
|
24
25
|
assert_respond_to TestModule, :error_grouping
|
|
25
26
|
assert_respond_to TestModule, :error_grouping=
|
|
26
27
|
end
|
|
27
28
|
|
|
28
|
-
test
|
|
29
|
+
test 'should set error_grouping to false default' do
|
|
29
30
|
assert_equal false, TestModule.error_grouping
|
|
30
31
|
end
|
|
31
32
|
|
|
32
|
-
test
|
|
33
|
+
test 'should add additional option: error_grouping_cache' do
|
|
33
34
|
assert_respond_to TestModule, :error_grouping_cache
|
|
34
35
|
assert_respond_to TestModule, :error_grouping_cache=
|
|
35
36
|
end
|
|
36
37
|
|
|
37
|
-
test
|
|
38
|
+
test 'should add additional option: error_grouping_period' do
|
|
38
39
|
assert_respond_to TestModule, :error_grouping_period
|
|
39
40
|
assert_respond_to TestModule, :error_grouping_period=
|
|
40
41
|
end
|
|
41
42
|
|
|
42
|
-
test
|
|
43
|
+
test 'shoud set error_grouping_period to 5.minutes default' do
|
|
43
44
|
assert_equal 300, TestModule.error_grouping_period
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
test
|
|
47
|
+
test 'should add additional option: notification_trigger' do
|
|
47
48
|
assert_respond_to TestModule, :notification_trigger
|
|
48
49
|
assert_respond_to TestModule, :notification_trigger=
|
|
49
50
|
end
|
|
50
51
|
|
|
51
|
-
test
|
|
52
|
-
assert_nil TestModule.error_count(
|
|
52
|
+
test 'should return errors count nil when not same error for .error_count' do
|
|
53
|
+
assert_nil TestModule.error_count('something')
|
|
53
54
|
end
|
|
54
55
|
|
|
55
|
-
test
|
|
56
|
-
TestModule.error_grouping_cache.write(
|
|
57
|
-
assert_equal 13, TestModule.error_count(
|
|
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')
|
|
58
59
|
end
|
|
59
60
|
|
|
60
|
-
test
|
|
61
|
-
TestModule.error_grouping_cache.stubs(:read).raises(RuntimeError.new
|
|
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'))
|
|
62
63
|
original_fallback = TestModule.fallback_cache_store
|
|
63
64
|
TestModule.expects(:fallback_cache_store).returns(original_fallback).at_least_once
|
|
64
65
|
|
|
65
|
-
assert_nil TestModule.error_count(
|
|
66
|
+
assert_nil TestModule.error_count('something_to_read')
|
|
66
67
|
end
|
|
67
68
|
|
|
68
|
-
test
|
|
69
|
+
test 'should save error with count for .save_error_count' do
|
|
69
70
|
count = rand(1..10)
|
|
70
71
|
|
|
71
|
-
TestModule.save_error_count(
|
|
72
|
-
assert_equal count, TestModule.error_grouping_cache.read(
|
|
72
|
+
TestModule.save_error_count('error_key', count)
|
|
73
|
+
assert_equal count, TestModule.error_grouping_cache.read('error_key')
|
|
73
74
|
end
|
|
74
75
|
|
|
75
|
-
test
|
|
76
|
-
TestModule.error_grouping_cache.stubs(:write).raises(RuntimeError.new
|
|
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'))
|
|
77
78
|
original_fallback = TestModule.fallback_cache_store
|
|
78
79
|
TestModule.expects(:fallback_cache_store).returns(original_fallback).at_least_once
|
|
79
80
|
|
|
80
|
-
assert TestModule.save_error_count(
|
|
81
|
+
assert TestModule.save_error_count('something_to_cache', rand(1..10))
|
|
81
82
|
end
|
|
82
83
|
|
|
83
|
-
test
|
|
84
|
+
test 'should save accumulated_errors_count into options' do
|
|
84
85
|
options = {}
|
|
85
86
|
TestModule.group_error!(@exception, options)
|
|
86
87
|
|
|
87
88
|
assert_equal 1, options[:accumulated_errors_count]
|
|
88
89
|
end
|
|
89
90
|
|
|
90
|
-
test
|
|
91
|
+
test 'should not group error if different exception in .group_error!' do
|
|
91
92
|
options1 = {}
|
|
92
|
-
TestModule.expects(:save_error_count).with{|key, count| key.is_a?(String) && count == 1}.times(4).returns(true)
|
|
93
|
+
TestModule.expects(:save_error_count).with { |key, count| key.is_a?(String) && count == 1 }.times(4).returns(true)
|
|
93
94
|
TestModule.group_error!(@exception, options1)
|
|
94
95
|
|
|
95
96
|
options2 = {}
|
|
96
|
-
TestModule.group_error!(NoMethodError.new(
|
|
97
|
+
TestModule.group_error!(NoMethodError.new('method not found'), options2)
|
|
97
98
|
|
|
98
99
|
assert_equal 1, options1[:accumulated_errors_count]
|
|
99
100
|
assert_equal 1, options2[:accumulated_errors_count]
|
|
100
101
|
end
|
|
101
102
|
|
|
102
|
-
test
|
|
103
|
+
test 'should not group error is same exception but different message or backtrace' do
|
|
103
104
|
options1 = {}
|
|
104
|
-
TestModule.expects(:save_error_count).with{|key, count| key.is_a?(String) && count == 1}.times(4).returns(true)
|
|
105
|
+
TestModule.expects(:save_error_count).with { |key, count| key.is_a?(String) && count == 1 }.times(4).returns(true)
|
|
105
106
|
TestModule.group_error!(@exception, options1)
|
|
106
107
|
|
|
107
108
|
options2 = {}
|
|
@@ -111,7 +112,7 @@ class ErrorGroupTest < ActiveSupport::TestCase
|
|
|
111
112
|
assert_equal 1, options2[:accumulated_errors_count]
|
|
112
113
|
end
|
|
113
114
|
|
|
114
|
-
test
|
|
115
|
+
test 'should group error if same exception and message' do
|
|
115
116
|
options = {}
|
|
116
117
|
|
|
117
118
|
10.times do |i|
|
|
@@ -122,7 +123,7 @@ class ErrorGroupTest < ActiveSupport::TestCase
|
|
|
122
123
|
assert_equal 10, options[:accumulated_errors_count]
|
|
123
124
|
end
|
|
124
125
|
|
|
125
|
-
test
|
|
126
|
+
test 'should group error if same exception and backtrace' do
|
|
126
127
|
options = {}
|
|
127
128
|
|
|
128
129
|
10.times do |i|
|
|
@@ -133,9 +134,9 @@ class ErrorGroupTest < ActiveSupport::TestCase
|
|
|
133
134
|
assert_equal 10, options[:accumulated_errors_count]
|
|
134
135
|
end
|
|
135
136
|
|
|
136
|
-
test
|
|
137
|
+
test 'should group error by that message have high priority' do
|
|
137
138
|
message_based_key = "exception:#{Zlib.crc32("RuntimeError\nmessage:ERROR")}"
|
|
138
|
-
backtrace_based_key = "exception:#{Zlib.crc32("RuntimeError\
|
|
139
|
+
backtrace_based_key = "exception:#{Zlib.crc32("RuntimeError\npath:/path/where/error/raised:1")}"
|
|
139
140
|
|
|
140
141
|
TestModule.save_error_count(message_based_key, 1)
|
|
141
142
|
TestModule.save_error_count(backtrace_based_key, 1)
|
|
@@ -146,7 +147,7 @@ class ErrorGroupTest < ActiveSupport::TestCase
|
|
|
146
147
|
TestModule.group_error!(@exception, {})
|
|
147
148
|
end
|
|
148
149
|
|
|
149
|
-
test
|
|
150
|
+
test 'use default formula if not specify notification_trigger in .send_notification?' do
|
|
150
151
|
TestModule.stubs(:notification_trigger).returns(nil)
|
|
151
152
|
|
|
152
153
|
count = 16
|
|
@@ -155,12 +156,12 @@ class ErrorGroupTest < ActiveSupport::TestCase
|
|
|
155
156
|
assert TestModule.send_notification?(@exception, count)
|
|
156
157
|
end
|
|
157
158
|
|
|
158
|
-
test
|
|
159
|
-
trigger =
|
|
159
|
+
test 'use specified trigger in .send_notification?' do
|
|
160
|
+
trigger = proc { |_exception, count| (count % 4).zero? }
|
|
160
161
|
TestModule.stubs(:notification_trigger).returns(trigger)
|
|
161
162
|
|
|
162
163
|
count = 16
|
|
163
164
|
trigger.expects(:call).with(@exception, count).returns(true)
|
|
164
165
|
assert TestModule.send_notification?(@exception, count)
|
|
165
166
|
end
|
|
166
|
-
end
|
|
167
|
+
end
|