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.
Files changed (134) hide show
  1. checksums.yaml +5 -5
  2. data/Appraisals +4 -3
  3. data/CHANGELOG.rdoc +57 -1
  4. data/CONTRIBUTING.md +21 -2
  5. data/Gemfile +3 -1
  6. data/README.md +106 -789
  7. data/Rakefile +4 -2
  8. data/docs/notifiers/campfire.md +50 -0
  9. data/docs/notifiers/custom.md +42 -0
  10. data/docs/notifiers/datadog.md +51 -0
  11. data/docs/notifiers/email.md +195 -0
  12. data/docs/notifiers/google_chat.md +31 -0
  13. data/docs/notifiers/hipchat.md +66 -0
  14. data/docs/notifiers/irc.md +97 -0
  15. data/docs/notifiers/mattermost.md +115 -0
  16. data/docs/notifiers/slack.md +161 -0
  17. data/docs/notifiers/sns.md +37 -0
  18. data/docs/notifiers/teams.md +54 -0
  19. data/docs/notifiers/webhook.md +60 -0
  20. data/examples/sample_app.rb +56 -0
  21. data/examples/sinatra/Gemfile +8 -6
  22. data/examples/sinatra/config.ru +3 -1
  23. data/examples/sinatra/sinatra_app.rb +19 -11
  24. data/exception_notification.gemspec +30 -23
  25. data/gemfiles/rails4_0.gemfile +1 -2
  26. data/gemfiles/rails4_1.gemfile +1 -2
  27. data/gemfiles/rails4_2.gemfile +1 -2
  28. data/gemfiles/rails5_0.gemfile +1 -2
  29. data/gemfiles/rails5_1.gemfile +7 -0
  30. data/gemfiles/rails5_2.gemfile +7 -0
  31. data/gemfiles/rails6_0.gemfile +7 -0
  32. data/lib/exception_notification.rb +3 -0
  33. data/lib/exception_notification/rack.rb +34 -27
  34. data/lib/exception_notification/rails.rb +3 -0
  35. data/lib/exception_notification/resque.rb +10 -10
  36. data/lib/exception_notification/sidekiq.rb +10 -12
  37. data/lib/exception_notification/version.rb +5 -0
  38. data/lib/exception_notifier.rb +79 -11
  39. data/lib/exception_notifier/base_notifier.rb +10 -5
  40. data/lib/exception_notifier/campfire_notifier.rb +14 -9
  41. data/lib/exception_notifier/datadog_notifier.rb +156 -0
  42. data/lib/exception_notifier/email_notifier.rb +78 -87
  43. data/lib/exception_notifier/google_chat_notifier.rb +44 -0
  44. data/lib/exception_notifier/hipchat_notifier.rb +16 -10
  45. data/lib/exception_notifier/irc_notifier.rb +38 -31
  46. data/lib/exception_notifier/mattermost_notifier.rb +54 -131
  47. data/lib/exception_notifier/modules/backtrace_cleaner.rb +2 -2
  48. data/lib/exception_notifier/modules/error_grouping.rb +87 -0
  49. data/lib/exception_notifier/modules/formatter.rb +121 -0
  50. data/lib/exception_notifier/notifier.rb +9 -6
  51. data/lib/exception_notifier/slack_notifier.rb +75 -32
  52. data/lib/exception_notifier/sns_notifier.rb +86 -0
  53. data/lib/exception_notifier/teams_notifier.rb +200 -0
  54. data/lib/exception_notifier/views/exception_notifier/_backtrace.html.erb +1 -1
  55. data/lib/exception_notifier/views/exception_notifier/_environment.text.erb +1 -1
  56. data/lib/exception_notifier/views/exception_notifier/_request.text.erb +1 -1
  57. data/lib/exception_notifier/views/exception_notifier/background_exception_notification.text.erb +9 -9
  58. data/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb +2 -4
  59. data/lib/exception_notifier/views/exception_notifier/exception_notification.text.erb +2 -2
  60. data/lib/exception_notifier/webhook_notifier.rb +19 -16
  61. data/lib/generators/exception_notification/install_generator.rb +11 -5
  62. data/lib/generators/exception_notification/templates/{exception_notification.rb → exception_notification.rb.erb} +14 -12
  63. data/test/exception_notification/rack_test.rb +90 -4
  64. data/test/exception_notification/resque_test.rb +54 -0
  65. data/test/exception_notifier/campfire_notifier_test.rb +66 -39
  66. data/test/exception_notifier/datadog_notifier_test.rb +153 -0
  67. data/test/exception_notifier/email_notifier_test.rb +301 -145
  68. data/test/exception_notifier/google_chat_notifier_test.rb +185 -0
  69. data/test/exception_notifier/hipchat_notifier_test.rb +112 -65
  70. data/test/exception_notifier/irc_notifier_test.rb +48 -30
  71. data/test/exception_notifier/mattermost_notifier_test.rb +218 -55
  72. data/test/exception_notifier/modules/error_grouping_test.rb +167 -0
  73. data/test/exception_notifier/modules/formatter_test.rb +152 -0
  74. data/test/exception_notifier/sidekiq_test.rb +9 -6
  75. data/test/exception_notifier/slack_notifier_test.rb +109 -59
  76. data/test/exception_notifier/sns_notifier_test.rb +123 -0
  77. data/test/exception_notifier/teams_notifier_test.rb +92 -0
  78. data/test/exception_notifier/webhook_notifier_test.rb +68 -38
  79. data/test/exception_notifier_test.rb +220 -37
  80. data/test/support/exception_notifier_helper.rb +14 -0
  81. data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.html.erb +0 -0
  82. data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.text.erb +0 -0
  83. data/test/{dummy/app → support}/views/exception_notifier/_new_section.html.erb +0 -0
  84. data/test/{dummy/app → support}/views/exception_notifier/_new_section.text.erb +0 -0
  85. data/test/test_helper.rb +14 -13
  86. metadata +154 -162
  87. data/test/dummy/.gitignore +0 -4
  88. data/test/dummy/Rakefile +0 -7
  89. data/test/dummy/app/controllers/application_controller.rb +0 -3
  90. data/test/dummy/app/controllers/posts_controller.rb +0 -30
  91. data/test/dummy/app/helpers/application_helper.rb +0 -2
  92. data/test/dummy/app/helpers/posts_helper.rb +0 -2
  93. data/test/dummy/app/models/post.rb +0 -2
  94. data/test/dummy/app/views/layouts/application.html.erb +0 -14
  95. data/test/dummy/app/views/posts/_form.html.erb +0 -0
  96. data/test/dummy/app/views/posts/new.html.erb +0 -0
  97. data/test/dummy/app/views/posts/show.html.erb +0 -0
  98. data/test/dummy/config.ru +0 -4
  99. data/test/dummy/config/application.rb +0 -42
  100. data/test/dummy/config/boot.rb +0 -6
  101. data/test/dummy/config/database.yml +0 -22
  102. data/test/dummy/config/environment.rb +0 -17
  103. data/test/dummy/config/environments/development.rb +0 -25
  104. data/test/dummy/config/environments/production.rb +0 -50
  105. data/test/dummy/config/environments/test.rb +0 -38
  106. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  107. data/test/dummy/config/initializers/inflections.rb +0 -10
  108. data/test/dummy/config/initializers/mime_types.rb +0 -5
  109. data/test/dummy/config/initializers/secret_token.rb +0 -8
  110. data/test/dummy/config/initializers/session_store.rb +0 -8
  111. data/test/dummy/config/locales/en.yml +0 -5
  112. data/test/dummy/config/routes.rb +0 -3
  113. data/test/dummy/db/migrate/20110729022608_create_posts.rb +0 -15
  114. data/test/dummy/db/schema.rb +0 -24
  115. data/test/dummy/db/seeds.rb +0 -7
  116. data/test/dummy/lib/tasks/.gitkeep +0 -0
  117. data/test/dummy/public/404.html +0 -26
  118. data/test/dummy/public/422.html +0 -26
  119. data/test/dummy/public/500.html +0 -26
  120. data/test/dummy/public/favicon.ico +0 -0
  121. data/test/dummy/public/images/rails.png +0 -0
  122. data/test/dummy/public/index.html +0 -239
  123. data/test/dummy/public/javascripts/application.js +0 -2
  124. data/test/dummy/public/javascripts/controls.js +0 -965
  125. data/test/dummy/public/javascripts/dragdrop.js +0 -974
  126. data/test/dummy/public/javascripts/effects.js +0 -1123
  127. data/test/dummy/public/javascripts/prototype.js +0 -6001
  128. data/test/dummy/public/javascripts/rails.js +0 -191
  129. data/test/dummy/public/robots.txt +0 -5
  130. data/test/dummy/public/stylesheets/.gitkeep +0 -0
  131. data/test/dummy/public/stylesheets/scaffold.css +0 -56
  132. data/test/dummy/script/rails +0 -6
  133. data/test/dummy/test/functional/posts_controller_test.rb +0 -218
  134. 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,25 +1,28 @@
1
- require "test_helper"
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 "sidekiq/cli"
8
+ require 'sidekiq/cli'
9
+ require 'sidekiq/testing'
7
10
 
8
- require "exception_notification/sidekiq"
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
- test "should call notify_exception when sidekiq raises an error" do
18
+ test 'should call notify_exception when sidekiq raises an error' do
16
19
  server = MockSidekiqServer.new
17
- message = Hash.new
20
+ message = {}
18
21
  exception = RuntimeError.new
19
22
 
20
23
  ExceptionNotifier.expects(:notify_exception).with(
21
24
  exception,
22
- :data => { :sidekiq => message }
25
+ data: { sidekiq: message }
23
26
  )
24
27
 
25
28
  server.handle_exception(exception, message)
@@ -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 "should send a slack notification if properly configured" do
15
+ test 'should send a slack notification if properly configured' do
14
16
  options = {
15
- webhook_url: "http://slack.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 "should send a slack notification without backtrace info if properly configured" do
26
+ test 'should send a slack notification without backtrace info if properly configured' do
25
27
  options = {
26
- webhook_url: "http://slack.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 "should send the notification to the specified channel" do
37
+ test 'should send the notification to the specified channel' do
36
38
  options = {
37
- webhook_url: "http://slack.webhook.url",
38
- channel: "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
- assert_equal slack_notifier.notifier.channel, options[:channel]
48
+ channel = slack_notifier.notifier.config.defaults[:channel]
49
+ assert_equal channel, options[:channel]
47
50
  end
48
51
 
49
- test "should send the notification to the specified username" do
52
+ test 'should send the notification to the specified username' do
50
53
  options = {
51
- webhook_url: "http://slack.webhook.url",
52
- username: "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,20 +60,49 @@ class SlackNotifierTest < ActiveSupport::TestCase
57
60
  slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
58
61
  slack_notifier.call(@exception)
59
62
 
60
- assert_equal slack_notifier.notifier.username, options[:username]
63
+ username = slack_notifier.notifier.config.defaults[:username]
64
+ assert_equal username, options[:username]
61
65
  end
62
66
 
63
- test "should pass the additional parameters to Slack::Notifier.ping" do
67
+ test 'should send the notification with specific backtrace lines' do
64
68
  options = {
65
- webhook_url: "http://slack.webhook.url",
66
- username: "test",
67
- custom_hook: "hook",
69
+ webhook_url: 'http://slack.webhook.url',
70
+ backtrace_lines: 1
71
+ }
72
+
73
+ Slack::Notifier.any_instance.expects(:ping).with('', fake_notification(@exception, {}, nil, 1))
74
+
75
+ slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
76
+ slack_notifier.call(@exception)
77
+ end
78
+
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
96
+ options = {
97
+ webhook_url: 'http://slack.webhook.url',
98
+ username: 'test',
99
+ custom_hook: 'hook',
68
100
  additional_parameters: {
69
- icon_url: "icon",
101
+ icon_url: 'icon'
70
102
  }
71
103
  }
72
104
 
73
- 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))
74
106
 
75
107
  slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
76
108
  slack_notifier.call(@exception)
@@ -85,55 +117,57 @@ class SlackNotifierTest < ActiveSupport::TestCase
85
117
  assert_nil slack_notifier.call(@exception)
86
118
  end
87
119
 
88
- test "should pass along environment data" do
120
+ test 'should pass along environment data' do
89
121
  options = {
90
- webhook_url: "http://slack.webhook.url",
91
- ignore_data_if: lambda {|k,v|
92
- "#{k}" == 'key_to_be_ignored' || v.is_a?(Hash)
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)
93
125
  }
94
126
  }
95
127
 
96
128
  notification_options = {
97
129
  env: {
98
- 'exception_notifier.exception_data' => {foo: 'bar', john: 'doe'}
130
+ 'exception_notifier.exception_data' => { foo: 'bar', john: 'doe' }
99
131
  },
100
132
  data: {
101
- 'user_id' => 5,
133
+ 'user_id' => 5,
102
134
  'key_to_be_ignored' => 'whatever',
103
- 'ignore_as_well' => {what: 'ever'}
135
+ 'ignore_as_well' => { what: 'ever' }
104
136
  }
105
137
  }
106
138
 
107
139
  expected_data_string = "foo: bar\njohn: doe\nuser_id: 5"
108
140
 
109
- Slack::Notifier.any_instance.expects(:ping).with('', fake_notification(@exception, expected_data_string))
141
+ Slack::Notifier.any_instance
142
+ .expects(:ping)
143
+ .with('', fake_notification(@exception, notification_options, expected_data_string))
110
144
  slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
111
145
  slack_notifier.call(@exception, notification_options)
112
146
  end
113
147
 
114
- test "should call pre/post_callback proc if specified" do
148
+ test 'should call pre/post_callback proc if specified' do
115
149
  post_callback_called = 0
116
150
  options = {
117
- webhook_url: "http://slack.webhook.url",
118
- username: "test",
119
- custom_hook: "hook",
120
- :pre_callback => proc { |opts, notifier, backtrace, message, message_opts|
121
- (message_opts[:attachments] = []) << { text: "#{backtrace.join("\n")}", color: 'danger' }
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' }
122
156
  },
123
- :post_callback => proc { |opts, notifier, backtrace, message, message_opts|
157
+ post_callback: proc { |_opts, _notifier, _backtrace, _message, _message_opts|
124
158
  post_callback_called = 1
125
159
  },
126
160
  additional_parameters: {
127
- icon_url: "icon",
161
+ icon_url: 'icon'
128
162
  }
129
163
  }
130
164
 
131
165
  Slack::Notifier.any_instance.expects(:ping).with('',
132
- {:icon_url => 'icon',
133
- :attachments => [
134
- {:text => fake_backtrace.join("\n"),
135
- :color => 'danger'}
136
- ]})
166
+ icon_url: 'icon',
167
+ attachments: [{
168
+ text: fake_backtrace.join("\n"),
169
+ color: 'danger'
170
+ }])
137
171
 
138
172
  slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
139
173
  slack_notifier.call(@exception)
@@ -143,11 +177,9 @@ class SlackNotifierTest < ActiveSupport::TestCase
143
177
  private
144
178
 
145
179
  def fake_exception
146
- begin
147
- 5/0
148
- rescue Exception => e
149
- e
150
- end
180
+ 5 / 0
181
+ rescue StandardError => e
182
+ e
151
183
  end
152
184
 
153
185
  def fake_exception_without_backtrace
@@ -156,24 +188,42 @@ class SlackNotifierTest < ActiveSupport::TestCase
156
188
 
157
189
  def fake_backtrace
158
190
  [
159
- "backtrace line 1",
160
- "backtrace line 2",
161
- "backtrace line 3",
162
- "backtrace line 4",
163
- "backtrace line 5",
164
- "backtrace line 6",
191
+ 'backtrace line 1', 'backtrace line 2', 'backtrace line 3',
192
+ 'backtrace line 4', 'backtrace line 5', 'backtrace line 6'
165
193
  ]
166
194
  end
167
195
 
168
- def fake_notification(exception = @exception, data_string = nil)
169
- text = "*An exception occurred while doing*: ` <>`\n"
196
+ def fake_cleaned_backtrace
197
+ fake_backtrace[2..-1]
198
+ end
170
199
 
171
- fields = [ { title: 'Exception', value: exception.message} ]
172
- fields.push({ title: 'Hostname', value: 'example.com' })
173
- fields.push({ title: 'Backtrace', value: "```#{fake_backtrace.join("\n")}```" }) if exception.backtrace
174
- fields.push({ title: 'Data', value: "```#{data_string}```" }) if data_string
200
+ def fake_notification(exception = @exception, notification_options = {},
201
+ data_string = nil, expected_backtrace_lines = 10, additional_fields = [])
175
202
 
176
- { attachments: [ color: 'danger', text: text, fields: fields, mrkdwn_in: %w(text fields) ] }
177
- end
203
+ exception_name = "*#{exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A'}* `#{exception.class}`"
204
+ if notification_options[:env].nil?
205
+ text = "#{exception_name} *occured in background*"
206
+ else
207
+ env = notification_options[:env]
178
208
 
209
+ kontroller = env['action_controller.instance']
210
+ request = "#{env['REQUEST_METHOD']} <#{env['REQUEST_URI']}>"
211
+
212
+ text = "#{exception_name} *occurred while* `#{request}`"
213
+ text += " *was processed by* `#{kontroller.controller_name}##{kontroller.action_name}`" if kontroller
214
+ end
215
+
216
+ text += "\n"
217
+
218
+ fields = [{ title: 'Exception', value: exception.message }]
219
+ fields.push(title: 'Hostname', value: 'example.com')
220
+ if exception.backtrace
221
+ formatted_backtrace = "```#{fake_cleaned_backtrace.first(expected_backtrace_lines).join("\n")}```"
222
+ fields.push(title: 'Backtrace', value: formatted_backtrace)
223
+ end
224
+ fields.push(title: 'Data', value: "```#{data_string}```") if data_string
225
+ additional_fields.each { |f| fields.push(f) }
226
+
227
+ { attachments: [color: 'danger', text: text, fields: fields, mrkdwn_in: %w[text fields]] }
228
+ end
179
229
  end