exception_notification 4.2.1 → 4.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +105 -780
  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 +30 -23
  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 +71 -40
  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 +17 -14
  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} +13 -11
  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 +59 -38
  66. data/test/exception_notifier/datadog_notifier_test.rb +153 -0
  67. data/test/exception_notifier/email_notifier_test.rb +279 -145
  68. data/test/exception_notifier/google_chat_notifier_test.rb +185 -0
  69. data/test/exception_notifier/hipchat_notifier_test.rb +105 -64
  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 -17
  75. data/test/exception_notifier/slack_notifier_test.rb +84 -62
  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 +52 -48
  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 -35
  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,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,43 +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({:url => 'http://localhost:8000'})
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], "ZeroDivisionError"
15
- assert_includes response[:body][:exception][:message], "divided by 0"
16
- assert_includes response[:body][:exception][:backtrace], "/exception_notification/test/webhook_notifier_test.rb:48"
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'
17
18
 
18
- assert response[:body][:request][:cookies].has_key?(:cookie_item1)
19
- assert_equal response[:body][:request][:url], "http://example.com/example"
20
- assert_equal response[:body][:request][:ip_address], "192.168.1.1"
21
- assert response[:body][:request][:environment].has_key?(:env_item1)
22
- assert_equal response[:body][:request][:controller], "#<ControllerName:0x007f9642a04d00>"
23
- assert response[:body][:request][:session].has_key?(:session_item1)
24
- assert response[:body][:request][:parameters].has_key?(:controller)
25
- assert response[:body][:data][:extra_data].has_key?(:data_item1)
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)
26
27
  end
27
28
 
28
- test "should send webhook notification with correct params data" do
29
+ test 'should send webhook notification with correct params data' do
29
30
  url = 'http://localhost:8000'
30
31
  fake_exception.stubs(:backtrace).returns('the backtrace')
31
- webhook = ExceptionNotifier::WebhookNotifier.new({:url => url})
32
+ webhook = ExceptionNotifier::WebhookNotifier.new(url: url)
32
33
 
33
34
  HTTParty.expects(:send).with(:post, url, fake_params)
34
35
 
35
36
  webhook.call(fake_exception)
36
37
  end
37
38
 
38
- test "should call pre/post_callback if specified" do
39
+ test 'should call pre/post_callback if specified' do
39
40
  HTTParty.stubs(:send).returns(fake_response)
40
- webhook = ExceptionNotifier::WebhookNotifier.new({:url => 'http://localhost:8000'})
41
+ webhook = ExceptionNotifier::WebhookNotifier.new(url: 'http://localhost:8000')
41
42
  webhook.call(fake_exception)
42
43
  end
43
44
 
@@ -45,50 +46,53 @@ class WebhookNotifierTest < ActiveSupport::TestCase
45
46
 
46
47
  def fake_response
47
48
  {
48
- :status => 200,
49
- :body => {
50
- :exception => {
51
- :error_class => 'ZeroDivisionError',
52
- :message => 'divided by 0',
53
- :backtrace => '/exception_notification/test/webhook_notifier_test.rb:48:in `/'
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 `/'
54
55
  },
55
- :data => {
56
- :extra_data => {:data_item1 => "datavalue1", :data_item2 => "datavalue2"}
56
+ data: {
57
+ extra_data: { data_item1: 'datavalue1', data_item2: 'datavalue2' }
57
58
  },
58
- :request => {
59
- :cookies => {:cookie_item1 => 'cookieitemvalue1', :cookie_item2 => 'cookieitemvalue2'},
60
- :url => 'http://example.com/example',
61
- :ip_address => '192.168.1.1',
62
- :environment => {:env_item1 => "envitem1", :env_item2 => "envitem2"},
63
- :controller => '#<ControllerName:0x007f9642a04d00>',
64
- :session => {:session_item1 => "sessionitem1", :session_item2 => "sessionitem2"},
65
- :parameters => {:action =>"index", :controller =>"projects"}
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' }
66
67
  }
67
68
  }
68
69
  }
69
70
  end
70
71
 
71
72
  def fake_params
72
- {
73
- :body => {
74
- :server => Socket.gethostname,
75
- :process => $$,
76
- :rails_root => Rails.root,
77
- :exception => {
78
- :error_class => 'ZeroDivisionError',
79
- :message => 'divided by 0'.inspect,
80
- :backtrace => 'the backtrace'
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
81
  },
82
- :data => {}
82
+ data: {}
83
83
  }
84
84
  }
85
+
86
+ params[:body][:rails_root] = Rails.root if defined?(::Rails) && Rails.respond_to?(:root)
87
+
88
+ params
85
89
  end
86
90
 
87
91
  def fake_exception
88
92
  @fake_exception ||= begin
89
- 5/0
90
- rescue Exception => e
91
- e
92
- end
93
+ 5 / 0
94
+ rescue StandardError => e
95
+ e
96
+ end
93
97
  end
94
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
- test "should have default ignored exceptions" do
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
- ['ActiveRecord::RecordNotFound', 'Mongoid::Errors::DocumentNotFound', 'AbstractController::ActionNotFound',
7
- 'ActionController::RoutingError', 'ActionController::UnknownFormat', 'ActionController::UrlGenerationError']
24
+ ['ActiveRecord::RecordNotFound', 'Mongoid::Errors::DocumentNotFound',
25
+ 'AbstractController::ActionNotFound', 'ActionController::RoutingError',
26
+ 'ActionController::UnknownFormat', 'ActionController::UrlGenerationError']
8
27
  end
9
28
 
10
- test "should have email notifier registered" do
29
+ test 'should have email notifier registered' do
11
30
  assert_equal ExceptionNotifier.notifiers, [:email]
12
31
  end
13
32
 
14
- test "should have a valid email notifier" do
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 "should allow register/unregister another notifier" do
40
+ test 'should allow register/unregister another notifier' do
22
41
  called = false
23
- proc_notifier = lambda { |exception, options| called = true }
42
+ proc_notifier = ->(_exception, _options) { called = true }
24
43
  ExceptionNotifier.register_exception_notifier(:proc, proc_notifier)
25
44
 
26
- assert_equal ExceptionNotifier.notifiers.sort, [:email, :proc]
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 "should allow select notifiers to send error to" do
56
+ test 'should allow select notifiers to send error to' do
38
57
  notifier1_calls = 0
39
- notifier1 = lambda { |exception, options| notifier1_calls += 1 }
58
+ notifier1 = ->(_exception, _options) { notifier1_calls += 1 }
40
59
  ExceptionNotifier.register_exception_notifier(:notifier1, notifier1)
41
60
 
42
61
  notifier2_calls = 0
43
- notifier2 = lambda { |exception, options| notifier2_calls += 1 }
62
+ notifier2 = ->(_exception, _options) { notifier2_calls += 1 }
44
63
  ExceptionNotifier.register_exception_notifier(:notifier2, notifier2)
45
64
 
46
- assert_equal ExceptionNotifier.notifiers.sort, [:email, :notifier1, :notifier2]
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, {:notifiers => :notifier1})
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, {:notifiers => :notifier2})
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 "should ignore exception if satisfies conditional ignore" do
67
- env = "production"
68
- ExceptionNotifier.ignore_if do |exception, options|
69
- env != "production"
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
- notifier_calls = 0
73
- test_notifier = lambda { |exception, options| notifier_calls += 1 }
74
- ExceptionNotifier.register_exception_notifier(:test, test_notifier)
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, {:notifiers => :test})
79
- assert_equal notifier_calls, 1
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
- env = "development"
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.clear_ignore_conditions!
86
- ExceptionNotifier.unregister_exception_notifier(:test)
157
+ refute ExceptionNotifier.notify_exception(exception)
87
158
  end
88
159
 
89
- test "should not send notification if one of ignored exceptions" do
90
- notifier_calls = 0
91
- test_notifier = lambda { |exception, options| notifier_calls += 1 }
92
- ExceptionNotifier.register_exception_notifier(:test, test_notifier)
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
- ExceptionNotifier.notify_exception(exception, {:notifiers => :test})
97
- assert_equal notifier_calls, 1
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
- ExceptionNotifier.notify_exception(exception, {:notifiers => :test, :ignore_exceptions => 'StandardError' })
100
- assert_equal notifier_calls, 1
224
+ module StandardErrorModule; end
101
225
 
102
- ExceptionNotifier.unregister_exception_notifier(:test)
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