super_exception_notifier 2.0.0

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