exception_notification 4.2.0 → 4.4.1

Sign up to get free protection for your applications and to get access to all the features.
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