super_exception_notifier 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. data/MIT-LICENSE +21 -0
  2. data/README.rdoc +528 -0
  3. data/VERSION.yml +4 -0
  4. data/init.rb +1 -0
  5. data/lib/exception_notifiable.rb +175 -0
  6. data/lib/exception_notifier.rb +185 -0
  7. data/lib/exception_notifier_helper.rb +60 -0
  8. data/lib/notifiable.rb +91 -0
  9. data/lib/super_exception_notifier/custom_exception_classes.rb +16 -0
  10. data/lib/super_exception_notifier/custom_exception_methods.rb +50 -0
  11. data/lib/super_exception_notifier/deprecated_methods.rb +60 -0
  12. data/lib/super_exception_notifier/git_blame.rb +52 -0
  13. data/lib/super_exception_notifier/helpful_hashes.rb +66 -0
  14. data/lib/super_exception_notifier/hooks_notifier.rb +55 -0
  15. data/lib/super_exception_notifier/notifiable_helper.rb +79 -0
  16. data/rails/app/views/exception_notifiable/400.html +5 -0
  17. data/rails/app/views/exception_notifiable/403.html +6 -0
  18. data/rails/app/views/exception_notifiable/404.html +6 -0
  19. data/rails/app/views/exception_notifiable/405.html +6 -0
  20. data/rails/app/views/exception_notifiable/410.html +7 -0
  21. data/rails/app/views/exception_notifiable/418.html +6 -0
  22. data/rails/app/views/exception_notifiable/422.html +5 -0
  23. data/rails/app/views/exception_notifiable/423.html +6 -0
  24. data/rails/app/views/exception_notifiable/501.html +8 -0
  25. data/rails/app/views/exception_notifiable/503.html +6 -0
  26. data/rails/app/views/exception_notifiable/method_disabled.html.erb +6 -0
  27. data/rails/init.rb +25 -0
  28. data/tasks/notified_task.rake +15 -0
  29. data/test/exception_notifiable_test.rb +34 -0
  30. data/test/exception_notifier_helper_test.rb +76 -0
  31. data/test/exception_notifier_test.rb +41 -0
  32. data/test/exception_notify_functional_test.rb +139 -0
  33. data/test/mocks/controllers.rb +82 -0
  34. data/test/notifiable_test.rb +79 -0
  35. data/test/test_helper.rb +32 -0
  36. data/views/exception_notifier/_backtrace.html.erb +1 -0
  37. data/views/exception_notifier/_environment.html.erb +14 -0
  38. data/views/exception_notifier/_inspect_model.html.erb +16 -0
  39. data/views/exception_notifier/_request.html.erb +8 -0
  40. data/views/exception_notifier/_session.html.erb +6 -0
  41. data/views/exception_notifier/_title.html.erb +3 -0
  42. data/views/exception_notifier/background_exception_notification.text.plain.erb +10 -0
  43. data/views/exception_notifier/exception_notification.text.plain.erb +15 -0
  44. metadata +119 -0
@@ -0,0 +1,8 @@
1
+ <!-- This file lives in gems/super_exception_notifier/rails/app/views/exception_notifiable/501.html. See Readme for instructions on customizing. -->
2
+ <div class="dialog">
3
+ <h1>Not Implemented</h1>
4
+
5
+ <p>Please remove bookmarks to this resource.</p>
6
+ <p>You have tried to access a feature that has not been implemented.</p>
7
+ <p>If you arrived here via a link from somewhere else please inform them of the broken link.</p>
8
+ </div>
@@ -0,0 +1,6 @@
1
+ <!-- This file lives in gems/super_exception_notifier/rails/app/views/exception_notifiable/503.html. See Readme for instructions on customizing. -->
2
+ <div class="dialog">
3
+ <h1>The server is temporarily unavailable.</h1>
4
+
5
+ <p>The resource you requested is temporarilly unavailable due to server overload, maintenance, or other downtime. Generally, this is a temporary state.</p>
6
+ </div>
@@ -0,0 +1,6 @@
1
+ <!-- This file lives in gems/super_exception_notifier/rails/app/views/exception_notifiable/method_disabled.html.erb. See Readme for instructions on customizing. -->
2
+ <div class="dialog">
3
+ <h1>Method Disabled.</h1>
4
+
5
+ <p>You attempted to access <%= params[:controller].humanize %> <%= params[:action] %> but that method is currently disabled. Reloading the page will not help unless and until the method is enabled by the system administrator.</p>
6
+ </div>
@@ -0,0 +1,25 @@
1
+ require "action_mailer"
2
+
3
+ require "super_exception_notifier/custom_exception_classes"
4
+ require "super_exception_notifier/custom_exception_methods"
5
+ require "super_exception_notifier/helpful_hashes"
6
+ require "super_exception_notifier/git_blame"
7
+ require "super_exception_notifier/deprecated_methods"
8
+ require "super_exception_notifier/hooks_notifier"
9
+ require "super_exception_notifier/notifiable_helper"
10
+
11
+ require "exception_notifier_helper" unless defined?(ExceptionNotifierHelper)
12
+ require "exception_notifier" unless defined?(ExceptionNotifier)
13
+ require "exception_notifiable" unless defined?(ExceptionNotifiable)
14
+ require "notifiable" unless defined?(Notifiable)
15
+
16
+ Object.class_eval do
17
+ include Notifiable
18
+ end
19
+
20
+ #It appears that the view path is auto-added by rails... hmmm.
21
+ #if ActionController::Base.respond_to?(:append_view_path)
22
+ # puts "view path before: #{ActionController::Base.view_paths}"
23
+ # ActionController::Base.append_view_path(File.join(File.dirname(__FILE__), 'app', 'views','exception_notifiable'))
24
+ # puts "view path After: #{ActionController::Base.view_paths}"
25
+ #end
@@ -0,0 +1,15 @@
1
+ class NotifiedTask < Rake::TaskLib
2
+ attr_accessor :name, :block
3
+
4
+ def initialize(name, &block)
5
+ @name = name
6
+ @block = block
7
+ define
8
+ end
9
+
10
+ def define
11
+ task name do |t|
12
+ notifiable { block.call }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ExceptionNotifiableTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @controller = BasicController.new
7
+ end
8
+
9
+ #Tests for the default values when ExceptionNotifiable is included in a controller
10
+ def test_default_http_status_codes
11
+ assert(BasicController.http_status_codes == HTTP_STATUS_CODES, "Default http_status_codes is incorrect")
12
+ end
13
+
14
+ def test_default_error_layout
15
+ assert(BasicController.error_layout == nil, "Default error_layout is incorrect")
16
+ end
17
+
18
+ def test_default_error_class_status_codes
19
+ assert(BasicController.error_class_status_codes == BasicController.codes_for_error_classes, "Default error_class_status_codes is incorrect")
20
+ end
21
+
22
+ def test_default_exception_notifiable_verbose
23
+ assert(BasicController.exception_notifiable_verbose == false, "Default exception_notifiable_verbose is incorrect")
24
+ end
25
+
26
+ def test_default_exception_notifiable_silent_exceptions
27
+ assert(BasicController.exception_notifiable_silent_exceptions == SILENT_EXCEPTIONS, "Default exception_notifiable_silent_exceptions is incorrect")
28
+ end
29
+
30
+ def test_default_exception_notifiable_notification_level
31
+ assert(BasicController.exception_notifiable_notification_level == [:render, :email, :web_hooks], "Default exception_notifiable_notification_level is incorrect")
32
+ end
33
+
34
+ end
@@ -0,0 +1,76 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require 'exception_notifier_helper'
3
+
4
+ class ExceptionNotifierHelperTest < Test::Unit::TestCase
5
+
6
+ class ExceptionNotifierHelperIncludeTarget
7
+ include ExceptionNotifierHelper
8
+ end
9
+
10
+ def setup
11
+ @helper = ExceptionNotifierHelperIncludeTarget.new
12
+ end
13
+
14
+ # No controller
15
+
16
+ def test_should_not_exclude_raw_post_parameters_if_no_controller
17
+ assert !@helper.exclude_raw_post_parameters?
18
+ end
19
+
20
+ # Controller, no filtering
21
+
22
+ class ControllerWithoutFilterParameters; end
23
+
24
+ def test_should_not_filter_env_values_for_raw_post_data_keys_if_controller_can_not_filter_parameters
25
+ stub_controller(ControllerWithoutFilterParameters.new)
26
+ assert @helper.filter_sensitive_post_data_from_env("RAW_POST_DATA", "secret").include?("secret")
27
+ end
28
+ def test_should_not_exclude_raw_post_parameters_if_controller_can_not_filter_parameters
29
+ stub_controller(ControllerWithoutFilterParameters.new)
30
+ assert !@helper.exclude_raw_post_parameters?
31
+ end
32
+ def test_should_return_params_if_controller_can_not_filter_parameters
33
+ stub_controller(ControllerWithoutFilterParameters.new)
34
+ assert_equal :params, @helper.filter_sensitive_post_data_parameters(:params)
35
+ end
36
+
37
+ # Controller with filter paramaters method, no params to filter
38
+
39
+ class ControllerWithFilterParametersThatDoesntFilter
40
+ def filter_parameters(params); params end
41
+ end
42
+
43
+ def test_should_filter_env_values_for_raw_post_data_keys_if_controller_can_filter_parameters
44
+ stub_controller(ControllerWithFilterParametersThatDoesntFilter.new)
45
+ assert !@helper.filter_sensitive_post_data_from_env("RAW_POST_DATA", "secret").include?("secret")
46
+ assert @helper.filter_sensitive_post_data_from_env("SOME_OTHER_KEY", "secret").include?("secret")
47
+ end
48
+ def test_should_exclude_raw_post_parameters_if_controller_can_filter_parameters
49
+ stub_controller(ControllerWithFilterParametersThatDoesntFilter.new)
50
+ assert @helper.exclude_raw_post_parameters?
51
+ end
52
+
53
+ # Controller with filter paramaters method, filtering a secret param
54
+
55
+ class ControllerWithFilterParametersThatDoesFilter
56
+ def filter_parameters(params); :filtered end
57
+ end
58
+
59
+ def test_should_delegate_param_filtering_to_controller_if_controller_can_filter_parameters
60
+ stub_controller(ControllerWithFilterParametersThatDoesFilter.new)
61
+ assert_equal :filtered, @helper.filter_sensitive_post_data_parameters(:secret)
62
+ end
63
+
64
+ def test_compat_mode_constant
65
+ if defined?(RAILS_GEM_VERSION)
66
+ assert_equal(ExceptionNotifierHelper::COMPAT_MODE, RAILS_GEM_VERSION >= 2)
67
+ else
68
+ assert_equal(ExceptionNotifierHelper::COMPAT_MODE, false)
69
+ end
70
+ end
71
+
72
+ private
73
+ def stub_controller(controller)
74
+ @helper.instance_variable_set(:@controller, controller)
75
+ end
76
+ end
@@ -0,0 +1,41 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require 'action_controller/test_process'
3
+
4
+ class ExceptionNotifierTest < Test::Unit::TestCase
5
+
6
+ def setup
7
+ @controller = ActionController::Base.new
8
+ @controller.request = ActionController::TestRequest.new
9
+ @controller.response = ActionController::TestResponse.new
10
+ @controller.params = {}
11
+ @controller.send(:initialize_current_url)
12
+ ActionController::Base.consider_all_requests_local = false
13
+ @@delivered_mail = []
14
+ ActionMailer::Base.class_eval do
15
+ def deliver!(mail = @mail)
16
+ @@delivered_mail << mail
17
+ end
18
+ end
19
+ end
20
+
21
+ def test_should_generate_message_without_controller
22
+ begin
23
+ raise 'problem'
24
+ rescue RuntimeError => e
25
+ assert_nothing_raised do
26
+ ExceptionNotifier.deliver_exception_notification(e)
27
+ end
28
+ end
29
+ end
30
+
31
+ def test_should_generate_message_with_controller
32
+ begin
33
+ raise 'problem'
34
+ rescue RuntimeError => e
35
+ assert_nothing_raised do
36
+ ExceptionNotifier.deliver_exception_notification(e, @controller, @controller.request)
37
+ end
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,139 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
+ require 'test/unit'
3
+
4
+ require File.join(File.dirname(__FILE__), 'mocks/controllers')
5
+
6
+ ActionController::Routing::Routes.clear!
7
+ ActionController::Routing::Routes.draw {|m| m.connect ':controller/:action/:id' }
8
+
9
+ class ExceptionNotifyFunctionalTest < ActionController::TestCase
10
+
11
+ def setup
12
+ @request = ActionController::TestRequest.new
13
+ @response = ActionController::TestResponse.new
14
+ ActionController::Base.consider_all_requests_local = false
15
+ @@delivered_mail = []
16
+ ActionMailer::Base.class_eval do
17
+ def deliver!(mail = @mail)
18
+ @@delivered_mail << mail
19
+ end
20
+ end
21
+ end
22
+
23
+ def test_view_path_200; assert_view_path_for_status_cd_is_string("200"); end
24
+ def test_view_path_400; assert_view_path_for_status_cd_is_string("400"); end
25
+ def test_view_path_403; assert_view_path_for_status_cd_is_string("403"); end
26
+ def test_view_path_404; assert_view_path_for_status_cd_is_string("404"); end
27
+ def test_view_path_405; assert_view_path_for_status_cd_is_string("405"); end
28
+ def test_view_path_410; assert_view_path_for_status_cd_is_string("410"); end
29
+ def test_view_path_418; assert_view_path_for_status_cd_is_string("422"); end
30
+ def test_view_path_422; assert_view_path_for_status_cd_is_string("422"); end
31
+ def test_view_path_423; assert_view_path_for_status_cd_is_string("423"); end
32
+ def test_view_path_500; assert_view_path_for_status_cd_is_string("500"); end
33
+ def test_view_path_501; assert_view_path_for_status_cd_is_string("501"); end
34
+ def test_view_path_503; assert_view_path_for_status_cd_is_string("503"); end
35
+ def test_view_path_nil; assert_view_path_for_status_cd_is_string(nil); end
36
+ def test_view_path_empty; assert_view_path_for_status_cd_is_string(""); end
37
+ def test_view_path_nonsense; assert_view_path_for_status_cd_is_string("slartibartfarst"); end
38
+ def test_view_path_class;
39
+ exception = SuperExceptionNotifier::CustomExceptionClasses::MethodDisabled
40
+ assert_view_path_for_class_is_string(exception);
41
+ assert ExceptionNotifier.get_view_path_for_class(exception).match("/rails/app/views/exception_notifiable/method_disabled.html.erb")
42
+ end
43
+ def test_view_path_class_nil; assert_view_path_for_class_is_string(nil); end
44
+ def test_view_path_class_empty; assert_view_path_for_class_is_string(""); end
45
+ def test_view_path_class_nonsense; assert_view_path_for_class_is_string("slartibartfarst"); end
46
+ def test_view_path_class_integer; assert_view_path_for_class_is_string(Integer); end
47
+
48
+ def test_exception_to_filenames
49
+ assert(["super_exception_notifier_custom_exception_classes_method_disabled", "method_disabled"] == ExceptionNotifier.exception_to_filenames(SuperExceptionNotifier::CustomExceptionClasses::MethodDisabled))
50
+ end
51
+
52
+ def test_old_style_where_requests_are_local
53
+ ActionController::Base.consider_all_requests_local = true
54
+ @controller = OldStyle.new
55
+ get "runtime_error"
56
+ assert_nothing_mailed
57
+ end
58
+
59
+ def test_new_style_where_requests_are_local
60
+ ActionController::Base.consider_all_requests_local = true
61
+ @controller = NewStyle.new
62
+ ExceptionNotifier.config[:skip_local_notification] = true
63
+ get "runtime_error"
64
+ assert_nothing_mailed
65
+ end
66
+
67
+ def test_old_style_runtime_error_sends_mail
68
+ @controller = OldStyle.new
69
+ get "runtime_error"
70
+ assert_error_mail_contains("This is a runtime error that we should be emailed about")
71
+ end
72
+
73
+ def test_old_style_record_not_found_does_not_send_mail
74
+ @controller = OldStyle.new
75
+ get "ar_record_not_found"
76
+ assert_nothing_mailed
77
+ end
78
+
79
+ def test_new_style_runtime_error_sends_mail
80
+ @controller = NewStyle.new
81
+ get "runtime_error"
82
+ assert_error_mail_contains("This is a runtime error that we should be emailed about")
83
+ end
84
+
85
+ def test_new_style_record_not_found_does_not_send_mail
86
+ @controller = NewStyle.new
87
+ get "ar_record_not_found"
88
+ assert_nothing_mailed
89
+ end
90
+
91
+ def test_controller_with_custom_silent_exceptions
92
+ @controller = CustomSilentExceptions.new
93
+ get "runtime_error"
94
+ assert_nothing_mailed
95
+ end
96
+
97
+ def test_controller_with_empty_silent_exceptions
98
+ @controller = EmptySilentExceptions.new
99
+ get "ar_record_not_found"
100
+ assert_error_mail_contains("ActiveRecord::RecordNotFound")
101
+ end
102
+
103
+ def test_controller_with_nil_silent_exceptions
104
+ @controller = NilSilentExceptions.new
105
+ get "ar_record_not_found"
106
+ assert_error_mail_contains("ActiveRecord::RecordNotFound")
107
+ end
108
+
109
+ def test_controller_with_default_silent_exceptions
110
+ @controller = DefaultSilentExceptions.new
111
+ get "unknown_controller"
112
+ assert_nothing_mailed
113
+ end
114
+
115
+ private
116
+
117
+ def assert_view_path_for_status_cd_is_string(status)
118
+ assert(ExceptionNotifier.get_view_path_for_status_code(status).is_a?(String), "View Path is not a string for status code '#{status}'")
119
+ end
120
+
121
+ def assert_view_path_for_class_is_string(exception)
122
+ assert(ExceptionNotifier.get_view_path_for_class(exception).is_a?(String), "View Path is not a string for exception '#{exception}'")
123
+ end
124
+
125
+ def assert_error_mail_contains(text)
126
+ assert(mailed_error.index(text),
127
+ "Expected mailed error body to contain '#{text}', but not found. \n actual contents: \n#{mailed_error}")
128
+ end
129
+
130
+ def assert_nothing_mailed
131
+ assert @@delivered_mail.empty?, "Expected to have NOT mailed out a notification about an error occuring, but mailed: \n#{@@delivered_mail}"
132
+ end
133
+
134
+ def mailed_error
135
+ assert @@delivered_mail.last, "Expected to have mailed out a notification about an error occuring, but none mailed"
136
+ @@delivered_mail.last.encoded
137
+ end
138
+
139
+ end
@@ -0,0 +1,82 @@
1
+ module Rails
2
+ def self.public_path
3
+ File.dirname(__FILE__)
4
+ end
5
+
6
+ def self.env
7
+ 'test'
8
+ end
9
+ end
10
+
11
+ class Application < ActionController::Base
12
+
13
+ def runtime_error
14
+ raise "This is a runtime error that we should be emailed about"
15
+ end
16
+
17
+ def ar_record_not_found
18
+ #From SuperExceptionNotifier::CustomExceptionMethods
19
+ record_not_found
20
+ end
21
+
22
+ def name_error
23
+ raise NameError
24
+ end
25
+
26
+ def unknown_controller
27
+ raise ActionController::UnknownController
28
+ end
29
+
30
+ def local_request?
31
+ false
32
+ end
33
+
34
+ end
35
+
36
+ class SpecialErrorThing < RuntimeError
37
+ end
38
+
39
+ class BasicController < Application
40
+ include ExceptionNotifiable
41
+ end
42
+
43
+ class CustomSilentExceptions < Application
44
+ include ExceptionNotifiable
45
+ self.exception_notifiable_verbose = false
46
+ self.exception_notifiable_silent_exceptions = [RuntimeError]
47
+ end
48
+
49
+ class EmptySilentExceptions < Application
50
+ include ExceptionNotifiable
51
+ self.exception_notifiable_verbose = false
52
+ self.exception_notifiable_silent_exceptions = []
53
+ end
54
+
55
+ class NilSilentExceptions < Application
56
+ include ExceptionNotifiable
57
+ self.exception_notifiable_verbose = false
58
+ self.exception_notifiable_silent_exceptions = nil
59
+ end
60
+
61
+ class DefaultSilentExceptions < Application
62
+ include ExceptionNotifiable
63
+ self.exception_notifiable_verbose = false
64
+ end
65
+
66
+ class OldStyle < Application
67
+ include ExceptionNotifiable
68
+ self.exception_notifiable_verbose = false
69
+ end
70
+
71
+ class NewStyle < Application
72
+ include ExceptionNotifiable
73
+ self.exception_notifiable_verbose = false
74
+
75
+ rescue_from ActiveRecord::RecordNotFound do |exception|
76
+ render :text => "404", :status => 404
77
+ end
78
+
79
+ rescue_from RuntimeError do |exception|
80
+ render :text => "500", :status => 500
81
+ end
82
+ end
@@ -0,0 +1,79 @@
1
+ # This is to test the ability to handle exceptions raised outside of the request response cycle
2
+ require File.expand_path(File.dirname(__FILE__) + '/test_helper')
3
+ require 'test/unit'
4
+
5
+ class Spaceship
6
+ # It is included by the init.rb in hte Object super class,
7
+ # so we don't actually need to do anything to get notifiable { method } goodness.
8
+ #include Notifiable
9
+ end
10
+
11
+ class SpaceCarrier < Spaceship
12
+ end
13
+
14
+ class NotifiableTest < Test::Unit::TestCase
15
+
16
+ def setup
17
+ @@delivered_mail = []
18
+ ActionMailer::Base.class_eval do
19
+ def deliver!(mail = @mail)
20
+ @@delivered_mail << mail
21
+ end
22
+ end
23
+ end
24
+
25
+ def test_notifiable_in_noisy_environment
26
+ @class = Spaceship.new
27
+ Spaceship.notifiable_noisy_environments = ['test']
28
+ ExceptionNotifier.config[:skip_local_notification] = true
29
+ assert_raises( AccessDenied ) {
30
+ notifiable { @class.access_denied }
31
+ assert_error_mail_contains("AccessDenied")
32
+ }
33
+ end
34
+
35
+ def test_notifiable_in_quiet_environment_not_skipping_local
36
+ @class = Spaceship.new
37
+ Spaceship.notifiable_noisy_environments = []
38
+ ExceptionNotifier.config[:skip_local_notification] = false
39
+ assert_raises( AccessDenied ) {
40
+ notifiable { @class.access_denied }
41
+ assert_error_mail_contains("AccessDenied")
42
+ }
43
+ end
44
+
45
+ def test_notifiable_in_quiet_environment_skipping_local
46
+ @class = Spaceship.new
47
+ Spaceship.notifiable_noisy_environments = []
48
+ ExceptionNotifier.config[:skip_local_notification] = true
49
+ assert_raises( AccessDenied ) {
50
+ notifiable { @class.access_denied }
51
+ assert_nothing_mailed
52
+ }
53
+ end
54
+
55
+ private
56
+
57
+ def assert_view_path_for_status_cd_is_string(status)
58
+ assert(ExceptionNotifier.get_view_path_for_status_code(status).is_a?(String), "View Path is not a string for status code '#{status}'")
59
+ end
60
+
61
+ def assert_view_path_for_class_is_string(exception)
62
+ assert(ExceptionNotifier.get_view_path_for_class(exception).is_a?(String), "View Path is not a string for exception '#{exception}'")
63
+ end
64
+
65
+ def assert_error_mail_contains(text)
66
+ assert(mailed_error.index(text),
67
+ "Expected mailed error body to contain '#{text}', but not found. \n actual contents: \n#{mailed_error}")
68
+ end
69
+
70
+ def assert_nothing_mailed
71
+ assert @@delivered_mail.empty?, "Expected to have NOT mailed out a notification about an error occuring, but mailed: \n#{@@delivered_mail}"
72
+ end
73
+
74
+ def mailed_error
75
+ assert @@delivered_mail.last, "Expected to have mailed out a notification about an error occuring, but none mailed"
76
+ @@delivered_mail.last.encoded
77
+ end
78
+
79
+ end