Commit 23a288e7002416df02b2aaa07916caf0c3614dd5
1 parent
4c5e63b3
Exists in
master
and in
1 other branch
updated hoptoad
Showing
32 changed files
with
2936 additions
and
1248 deletions
Show diff stats
vendor/plugins/hoptoad_notifier/INSTALL
| 1 | -HoptoadNotifier | |
| 2 | -=============== | |
| 3 | - | |
| 4 | -This is the notifier plugin for integrating apps with Hoptoad. | |
| 5 | - | |
| 6 | -When an uncaught exception occurs, HoptoadNotifier will POST the relevant data | |
| 7 | -to the Hoptoad server specified in your environment. | |
| 8 | - | |
| 9 | - | |
| 10 | -INSTALLATION | |
| 11 | ------------- | |
| 12 | - | |
| 13 | -REMOVE EXCEPTION_NOTIFIER | |
| 14 | - | |
| 15 | -In your ApplicationController, REMOVE this line: | |
| 16 | - | |
| 17 | - include ExceptionNotifiable | |
| 18 | - | |
| 19 | -In your config/environment* files, remove all references to ExceptionNotifier | |
| 20 | - | |
| 21 | -Remove the vendor/plugins/exception_notifier directory. | |
| 22 | - | |
| 23 | -INSTALL HOPTOAD_NOTIFIER | |
| 24 | - | |
| 25 | -From your project's RAILS_ROOT, run: | |
| 26 | - | |
| 27 | - script/plugin install git://github.com/thoughtbot/hoptoad_notifier.git | |
| 28 | - | |
| 29 | -CONFIGURATION | |
| 1 | +=== Configuration | |
| 30 | 2 | |
| 31 | 3 | You should have something like this in config/initializers/hoptoad.rb. |
| 32 | 4 | |
| 33 | 5 | HoptoadNotifier.configure do |config| |
| 34 | 6 | config.api_key = '1234567890abcdef' |
| 35 | 7 | end |
| 36 | - | |
| 8 | + | |
| 37 | 9 | (Please note that this configuration should be in a global configuration, and |
| 38 | -is *not* enrivonment-specific. Hoptoad is smart enough to know what errors are | |
| 10 | +is *not* environment-specific. Hoptoad is smart enough to know what errors are | |
| 39 | 11 | caused by what environments, so your staging errors don't get mixed in with |
| 40 | 12 | your production errors.) |
| 41 | 13 | |
| 42 | -After this is in place, all exceptions will be logged to Hoptoad where they can | |
| 43 | -be aggregated, filtered, sorted, analyzed, massaged, and searched. | |
| 44 | - | |
| 45 | -** NOTE FOR RAILS 1.2.* USERS: ** | |
| 46 | -You will need to copy the hoptoad_notifier_tasks.rake file into your | |
| 47 | -RAILS_ROOT/lib/tasks directory in order for the following to work: | |
| 48 | - | |
| 49 | -You can test that hoptoad is working in your production environment by using | |
| 14 | +You can test that Hoptoad is working in your production environment by using | |
| 50 | 15 | this rake task (from RAILS_ROOT): |
| 51 | 16 | |
| 52 | 17 | rake hoptoad:test |
| 53 | 18 | |
| 54 | -If everything is configured properly, that task will send a notice to hoptoad | |
| 19 | +If everything is configured properly, that task will send a notice to Hoptoad | |
| 55 | 20 | which will be visible immediately. |
| 21 | + | |
| 22 | +NOTE FOR RAILS 1.2.* USERS: | |
| 23 | + | |
| 24 | +You will need to copy the hoptoad_notifier_tasks.rake file into your | |
| 25 | +RAILS_ROOT/lib/tasks directory in order for the rake hoptoad:test task to work. | ... | ... |
vendor/plugins/hoptoad_notifier/README
| ... | ... | @@ -1,185 +0,0 @@ |
| 1 | -HoptoadNotifier | |
| 2 | -=============== | |
| 3 | - | |
| 4 | -This is the notifier plugin for integrating apps with Hoptoad. | |
| 5 | - | |
| 6 | -When an uncaught exception occurs, HoptoadNotifier will POST the relevant data | |
| 7 | -to the Hoptoad server specified in your environment. | |
| 8 | - | |
| 9 | -INSTALLATION | |
| 10 | ------------- | |
| 11 | - | |
| 12 | -REMOVE EXCEPTION_NOTIFIER | |
| 13 | - | |
| 14 | -In your ApplicationController, REMOVE this line: | |
| 15 | - | |
| 16 | - include ExceptionNotifiable | |
| 17 | - | |
| 18 | -In your config/environment* files, remove all references to ExceptionNotifier | |
| 19 | - | |
| 20 | -Remove the vendor/plugins/exception_notifier directory. | |
| 21 | - | |
| 22 | -INSTALL HOPTOAD_NOTIFIER | |
| 23 | - | |
| 24 | -From your project's RAILS_ROOT, run: | |
| 25 | - | |
| 26 | - script/plugin install git://github.com/thoughtbot/hoptoad_notifier.git | |
| 27 | - | |
| 28 | -CONFIGURATION | |
| 29 | - | |
| 30 | -You should have something like this in config/initializers/hoptoad.rb. | |
| 31 | - | |
| 32 | - HoptoadNotifier.configure do |config| | |
| 33 | - config.api_key = '1234567890abcdef' | |
| 34 | - end | |
| 35 | - | |
| 36 | -(Please note that this configuration should be in a global configuration, and | |
| 37 | -is *not* environment-specific. Hoptoad is smart enough to know what errors are | |
| 38 | -caused by what environments, so your staging errors don't get mixed in with | |
| 39 | -your production errors.) | |
| 40 | - | |
| 41 | -That should be it! Now all exceptions will be logged to Hoptoad where they can | |
| 42 | -be aggregated, filtered, sorted, analyzed, massaged, and searched. In previous | |
| 43 | -releases you had to include HoptoadNotifier::Catcher into your | |
| 44 | -ApplicationController, but the plugin takes care of that now. | |
| 45 | - | |
| 46 | -** NOTE FOR RAILS 1.2.* USERS: ** | |
| 47 | -You will need to copy the hoptoad_notifier_tasks.rake file into your | |
| 48 | -RAILS_ROOT/lib/tasks directory in order for the following to work: | |
| 49 | - | |
| 50 | -You can test that hoptoad is working in your production environment by using | |
| 51 | -this rake task (from RAILS_ROOT): | |
| 52 | - | |
| 53 | - rake hoptoad:test | |
| 54 | - | |
| 55 | -If everything is configured properly, that task will send a notice to hoptoad | |
| 56 | -which will be visible immediately. | |
| 57 | - | |
| 58 | -USAGE | |
| 59 | - | |
| 60 | -For the most part, hoptoad works for itself. Once you've included the notifier | |
| 61 | -in your ApplicationController (which is now done automatically by the plugin), | |
| 62 | -all errors will be rescued by the #rescue_action_in_public provided by the plugin. | |
| 63 | - | |
| 64 | -If you want to log arbitrary things which you've rescued yourself from a | |
| 65 | -controller, you can do something like this: | |
| 66 | - | |
| 67 | - ... | |
| 68 | - rescue => ex | |
| 69 | - notify_hoptoad(ex) | |
| 70 | - flash[:failure] = 'Encryptions could not be rerouted, try again.' | |
| 71 | - end | |
| 72 | - ... | |
| 73 | - | |
| 74 | -The #notify_hoptoad call will send the notice over to hoptoad for later | |
| 75 | -analysis. | |
| 76 | - | |
| 77 | -TRACKING DEPLOYMENTS IN HOPTOAD | |
| 78 | - | |
| 79 | -Paying Hoptoad plans support the ability to track deployments of your application in Hoptoad. | |
| 80 | -By notify Hoptoad of your application deployments, all errors are resolved when a deploy occurs, | |
| 81 | -so that you'll be notified again about any errors that reoccur after a deployment. | |
| 82 | - | |
| 83 | -Additionally, it's possible to review the errors in Hoptoad that occurred before and after a deploy. | |
| 84 | - | |
| 85 | -When Hoptoad is installed as a plugin this functionality is loaded automatically (if you have Capistrano version 2.0.0 or greater). | |
| 86 | - | |
| 87 | -When Hoptoad installed as a gem, you need to add | |
| 88 | - require 'hoptoad_notifier/recipes/hoptoad' | |
| 89 | -to your deploy.rb | |
| 90 | - | |
| 91 | -GOING BEYOND EXCEPTIONS | |
| 92 | - | |
| 93 | -You can also pass a hash to notify_hoptoad method and store whatever you want, not just an exception. And you can also use it anywhere, not just in controllers: | |
| 94 | - | |
| 95 | - begin | |
| 96 | - params = { | |
| 97 | - # params that you pass to a method that can throw an exception | |
| 98 | - } | |
| 99 | - my_unpredicable_method(params) | |
| 100 | - rescue => e | |
| 101 | - HoptoadNotifier.notify( | |
| 102 | - :error_class => "Special Error", | |
| 103 | - :error_message => "Special Error: #{e.message}", | |
| 104 | - :request => { :params => params } | |
| 105 | - ) | |
| 106 | - end | |
| 107 | - | |
| 108 | -While in your controllers you use the notify_hoptoad method, anywhere else in your code, use HoptoadNotifier.notify. Hoptoad will get all the information about the error itself. As for a hash, these are the keys you should pass: | |
| 109 | - | |
| 110 | - * :error_class – Use this to group similar errors together. When Hoptoad catches an exception it sends the class name of that exception object. | |
| 111 | - * :error_message – This is the title of the error you see in the errors list. For exceptions it is "#{exception.class.name}: #{exception.message}" | |
| 112 | - * :request – While there are several ways to send additional data to Hoptoad, passing a Hash with :params key as :request as in the example above is the most common use case. When Hoptoad catches an exception in a controller, the actual HTTP client request is being sent using this key. | |
| 113 | - | |
| 114 | -Hoptoad merges the hash you pass with these default options: | |
| 115 | - | |
| 116 | - def default_notice_options | |
| 117 | - { | |
| 118 | - :api_key => HoptoadNotifier.api_key, | |
| 119 | - :error_message => 'Notification', | |
| 120 | - :backtrace => caller, | |
| 121 | - :request => {}, | |
| 122 | - :session => {}, | |
| 123 | - :environment => ENV.to_hash | |
| 124 | - } | |
| 125 | - end | |
| 126 | - | |
| 127 | -You can override any of those parameters. | |
| 128 | - | |
| 129 | -FILTERING | |
| 130 | - | |
| 131 | -You can specify a whitelist of errors, that Hoptoad will not report on. Use | |
| 132 | -this feature when you are so apathetic to certain errors that you don't want | |
| 133 | -them even logged. | |
| 134 | - | |
| 135 | -This filter will only be applied to automatic notifications, not manual | |
| 136 | -notifications (when #notify is called directly). | |
| 137 | - | |
| 138 | -Hoptoad ignores the following exceptions by default: | |
| 139 | - ActiveRecord::RecordNotFound | |
| 140 | - ActionController::RoutingError | |
| 141 | - ActionController::InvalidAuthenticityToken | |
| 142 | - CGI::Session::CookieStore::TamperedWithCookie | |
| 143 | - | |
| 144 | -To ignore errors in addition to those, specify their names in your Hoptoad | |
| 145 | -configuration block. | |
| 146 | - | |
| 147 | - HoptoadNotifier.configure do |config| | |
| 148 | - config.api_key = '1234567890abcdef' | |
| 149 | - config.ignore << ActiveRecord::IgnoreThisError | |
| 150 | - end | |
| 151 | - | |
| 152 | -To ignore *only* certain errors (and override the defaults), use the | |
| 153 | -#ignore_only attribute. | |
| 154 | - | |
| 155 | - HoptoadNotifier.configure do |config| | |
| 156 | - config.api_key = '1234567890abcdef' | |
| 157 | - config.ignore_only = [ActiveRecord::IgnoreThisError] | |
| 158 | - end | |
| 159 | - | |
| 160 | -To ignore certain user agents, add in the #ignore_user_agent attribute as a | |
| 161 | -string or regexp: | |
| 162 | - | |
| 163 | - HoptoadNotifier.configure do |config| | |
| 164 | - config.api_key = '1234567890abcdef' | |
| 165 | - config.ignore_user_agent << /Ignored/ | |
| 166 | - config.ignore_user_agent << 'IgnoredUserAgent' | |
| 167 | - end | |
| 168 | - | |
| 169 | -TESTING | |
| 170 | - | |
| 171 | -When you run your tests, you might notice that the hoptoad service is recording | |
| 172 | -notices generated using #notify when you don't expect it to. You can | |
| 173 | -use code like this in your test_helper.rb to redefine that method so those | |
| 174 | -errors are not reported while running tests. | |
| 175 | - | |
| 176 | - module HoptoadNotifier::Catcher | |
| 177 | - def notify(thing) | |
| 178 | - # do nothing. | |
| 179 | - end | |
| 180 | - end | |
| 181 | - | |
| 182 | -THANKS | |
| 183 | - | |
| 184 | -Thanks to Eugene Bolshakov for the excellent write-up on GOING BEYOND EXCEPTIONS, which we have included above. | |
| 185 | - |
| ... | ... | @@ -0,0 +1,227 @@ |
| 1 | += HoptoadNotifier | |
| 2 | + | |
| 3 | +This is the notifier plugin for integrating apps with Hoptoad. | |
| 4 | + | |
| 5 | +When an uncaught exception occurs, HoptoadNotifier will POST the relevant data | |
| 6 | +to the Hoptoad server specified in your environment. | |
| 7 | + | |
| 8 | +== Help | |
| 9 | + | |
| 10 | +* {IRC}[irc://irc.freenode.net/thoughtbot] | |
| 11 | +* {mailing list}[http://groups.google.com/group/hoptoad-notifier-dev] | |
| 12 | + | |
| 13 | +== Installation | |
| 14 | + | |
| 15 | +=== Remove exception_notifier | |
| 16 | + | |
| 17 | +in your ApplicationController, REMOVE this line: | |
| 18 | + | |
| 19 | + include ExceptionNotifiable | |
| 20 | + | |
| 21 | +In your config/environment* files, remove all references to ExceptionNotifier | |
| 22 | + | |
| 23 | +Remove the vendor/plugins/exception_notifier directory. | |
| 24 | + | |
| 25 | +=== Install hoptoad_notifier | |
| 26 | + | |
| 27 | +from your project's RAILS_ROOT, run: | |
| 28 | + | |
| 29 | + script/plugin install -f git://github.com/thoughtbot/hoptoad_notifier.git | |
| 30 | + | |
| 31 | +=== Configuration | |
| 32 | + | |
| 33 | +You should have something like this in config/initializers/hoptoad.rb. | |
| 34 | + | |
| 35 | + HoptoadNotifier.configure do |config| | |
| 36 | + config.api_key = '1234567890abcdef' | |
| 37 | + end | |
| 38 | + | |
| 39 | +(Please note that this configuration should be in a global configuration, and | |
| 40 | +is *not* environment-specific. Hoptoad is smart enough to know what errors are | |
| 41 | +caused by what environments, so your staging errors don't get mixed in with | |
| 42 | +your production errors.) | |
| 43 | + | |
| 44 | +After adding to your config/initializers like this you must restart your | |
| 45 | +server. This will not affect the rake task but it bears stating. | |
| 46 | + | |
| 47 | +That should be it! Now all exceptions will be logged to Hoptoad where they can | |
| 48 | +be aggregated, filtered, sorted, analyzed, massaged, and searched. In previous | |
| 49 | +releases you had to include HoptoadNotifier::Catcher into your | |
| 50 | +ApplicationController, but the plugin takes care of that now. | |
| 51 | + | |
| 52 | +You can test that Hoptoad is working in your production environment by using | |
| 53 | +this rake task (from RAILS_ROOT): | |
| 54 | + | |
| 55 | + rake hoptoad:test | |
| 56 | + | |
| 57 | +If everything is configured properly, that task will send a notice to Hoptoad | |
| 58 | +which will be visible immediately. | |
| 59 | + | |
| 60 | +=== NOTE FOR RAILS 1.2.* USERS: | |
| 61 | + | |
| 62 | +You will need to copy the hoptoad_notifier_tasks.rake file into your | |
| 63 | +RAILS_ROOT/lib/tasks directory in order for the rake hoptoad:test task to work. | |
| 64 | + | |
| 65 | +== Usage | |
| 66 | + | |
| 67 | +for the most part, Hoptoad works for itself. Once you've included the notifier | |
| 68 | +in your ApplicationController (which is now done automatically by the plugin), | |
| 69 | +all errors will be rescued by the #rescue_action_in_public provided by the plugin. | |
| 70 | + | |
| 71 | +If you want to log arbitrary things which you've rescued yourself from a | |
| 72 | +controller, you can do something like this: | |
| 73 | + | |
| 74 | + ... | |
| 75 | + rescue => ex | |
| 76 | + notify_hoptoad(ex) | |
| 77 | + flash[:failure] = 'Encryptions could not be rerouted, try again.' | |
| 78 | + end | |
| 79 | + ... | |
| 80 | + | |
| 81 | +The #notify_hoptoad call will send the notice over to Hoptoad for later analysis. While in your controllers you use the notify_hoptoad method, anywhere else in your code, use HoptoadNotifier.notify. | |
| 82 | + | |
| 83 | +To perform custom error processing after Hoptoad has been notified, define the instance method #rescue_action_in_public_without_hoptoad(exception) in your controller. | |
| 84 | + | |
| 85 | +== Tracking deployments in Hoptoad | |
| 86 | + | |
| 87 | +Paying Hoptoad plans support the ability to track deployments of your application in Hoptoad. | |
| 88 | +By notifying Hoptoad of your application deployments, all errors are resolved when a deploy occurs, | |
| 89 | +so that you'll be notified again about any errors that reoccur after a deployment. | |
| 90 | + | |
| 91 | +Additionally, it's possible to review the errors in Hoptoad that occurred before and after a deploy. | |
| 92 | + | |
| 93 | +When Hoptoad is installed as a plugin this functionality is loaded automatically (if you have Capistrano version 2.0.0 or greater). | |
| 94 | + | |
| 95 | +When Hoptoad is installed as a gem, you need to add | |
| 96 | + | |
| 97 | + require 'hoptoad_notifier/recipes/hoptoad' | |
| 98 | + | |
| 99 | +to your deploy.rb | |
| 100 | + | |
| 101 | +== Going beyond exceptions | |
| 102 | + | |
| 103 | +You can also pass a hash to notify_hoptoad method and store whatever you want, not just an exception. And you can also use it anywhere, not just in controllers: | |
| 104 | + | |
| 105 | + begin | |
| 106 | + params = { | |
| 107 | + # params that you pass to a method that can throw an exception | |
| 108 | + } | |
| 109 | + my_unpredicable_method(params) | |
| 110 | + rescue => e | |
| 111 | + HoptoadNotifier.notify( | |
| 112 | + :error_class => "Special Error", | |
| 113 | + :error_message => "Special Error: #{e.message}", | |
| 114 | + :parameters => params | |
| 115 | + ) | |
| 116 | + end | |
| 117 | + | |
| 118 | +While in your controllers you use the notify_hoptoad method, anywhere else in your code, use HoptoadNotifier.notify. Hoptoad will get all the information about the error itself. As for a hash, these are the keys you should pass: | |
| 119 | + | |
| 120 | +* :error_class – Use this to group similar errors together. When Hoptoad catches an exception it sends the class name of that exception object. | |
| 121 | +* :error_message – This is the title of the error you see in the errors list. For exceptions it is "#{exception.class.name}: #{exception.message}" | |
| 122 | +* :parameters – While there are several ways to send additional data to Hoptoad, passing a Hash as :parameters as in the example above is the most common use case. When Hoptoad catches an exception in a controller, the actual HTTP client request parameters are sent using this key. | |
| 123 | + | |
| 124 | +Hoptoad merges the hash you pass with these default options: | |
| 125 | + | |
| 126 | + { | |
| 127 | + :api_key => HoptoadNotifier.api_key, | |
| 128 | + :error_message => 'Notification', | |
| 129 | + :backtrace => caller, | |
| 130 | + :parameters => {}, | |
| 131 | + :session => {} | |
| 132 | + } | |
| 133 | + | |
| 134 | +You can override any of those parameters. | |
| 135 | + | |
| 136 | +== Filtering | |
| 137 | + | |
| 138 | +You can specify a whitelist of errors, that Hoptoad will not report on. Use | |
| 139 | +this feature when you are so apathetic to certain errors that you don't want | |
| 140 | +them even logged. | |
| 141 | + | |
| 142 | +This filter will only be applied to automatic notifications, not manual | |
| 143 | +notifications (when #notify is called directly). | |
| 144 | + | |
| 145 | +Hoptoad ignores the following exceptions by default: | |
| 146 | + | |
| 147 | + ActiveRecord::RecordNotFound | |
| 148 | + ActionController::RoutingError | |
| 149 | + ActionController::InvalidAuthenticityToken | |
| 150 | + ActionController::UnknownAction | |
| 151 | + CGI::Session::CookieStore::TamperedWithCookie | |
| 152 | + | |
| 153 | +To ignore errors in addition to those, specify their names in your Hoptoad | |
| 154 | +configuration block. | |
| 155 | + | |
| 156 | + HoptoadNotifier.configure do |config| | |
| 157 | + config.api_key = '1234567890abcdef' | |
| 158 | + config.ignore << ActiveRecord::IgnoreThisError | |
| 159 | + end | |
| 160 | + | |
| 161 | +To ignore *only* certain errors (and override the defaults), use the | |
| 162 | +#ignore_only attribute. | |
| 163 | + | |
| 164 | + HoptoadNotifier.configure do |config| | |
| 165 | + config.api_key = '1234567890abcdef' | |
| 166 | + config.ignore_only = [ActiveRecord::IgnoreThisError] | |
| 167 | + end | |
| 168 | + | |
| 169 | +To ignore certain user agents, add in the #ignore_user_agent attribute as a | |
| 170 | +string or regexp: | |
| 171 | + | |
| 172 | + HoptoadNotifier.configure do |config| | |
| 173 | + config.api_key = '1234567890abcdef' | |
| 174 | + config.ignore_user_agent << /Ignored/ | |
| 175 | + config.ignore_user_agent << 'IgnoredUserAgent' | |
| 176 | + end | |
| 177 | + | |
| 178 | +To ignore exceptions based on other conditions, use #ignore_by_filter: | |
| 179 | + | |
| 180 | + HoptoadNotifier.configure do |config| | |
| 181 | + config.api_key = '1234567890abcdef' | |
| 182 | + config.ignore_by_filter do |exception_data| | |
| 183 | + true if exception_data[:error_class] == "RuntimeError" | |
| 184 | + end | |
| 185 | + end | |
| 186 | + | |
| 187 | +To replace sensitive information sent to the Hoptoad service with [FILTERED] use #params_filters: | |
| 188 | + | |
| 189 | + HoptoadNotifier.configure do |config| | |
| 190 | + config.api_key = '1234567890abcdef' | |
| 191 | + config.params_filters << "credit_card_number" | |
| 192 | + end | |
| 193 | + | |
| 194 | +Note that, when rescuing exceptions within an ActionController method, | |
| 195 | +hoptoad_notifier will reuse filters specified by #filter_params_logging. | |
| 196 | + | |
| 197 | +== Testing | |
| 198 | + | |
| 199 | +When you run your tests, you might notice that the Hoptoad service is recording | |
| 200 | +notices generated using #notify when you don't expect it to. You can | |
| 201 | +use code like this in your test_helper.rb to redefine that method so those | |
| 202 | +errors are not reported while running tests. | |
| 203 | + | |
| 204 | + module HoptoadNotifier::Catcher | |
| 205 | + def notify(thing) | |
| 206 | + # do nothing. | |
| 207 | + end | |
| 208 | + end | |
| 209 | + | |
| 210 | +== Supported rails versions | |
| 211 | + | |
| 212 | +the notifier currently supports the following versions of Rails: | |
| 213 | + | |
| 214 | +* 1.2.6 | |
| 215 | +* 2.0.2 | |
| 216 | +* 2.1.0 | |
| 217 | +* 2.1.2 | |
| 218 | +* 2.2.2 | |
| 219 | +* 2.3.2 | |
| 220 | +* 2.3.3 | |
| 221 | +* 2.3.4 | |
| 222 | + | |
| 223 | +Please open up a support ticket on Tender ( http://help.hoptoadapp.com ) if you're using a version of Rails that is not listed above and the notifier is not working properly. | |
| 224 | + | |
| 225 | +== Thanks | |
| 226 | + | |
| 227 | +Thanks to Eugene Bolshakov for the excellent write-up on GOING BEYOND EXCEPTIONS, which we have included above. | ... | ... |
vendor/plugins/hoptoad_notifier/Rakefile
| ... | ... | @@ -12,15 +12,6 @@ Rake::TestTask.new(:test) do |t| |
| 12 | 12 | t.verbose = true |
| 13 | 13 | end |
| 14 | 14 | |
| 15 | -desc 'Generate documentation for the hoptoad_notifier plugin.' | |
| 16 | -Rake::RDocTask.new(:rdoc) do |rdoc| | |
| 17 | - rdoc.rdoc_dir = 'rdoc' | |
| 18 | - rdoc.title = 'HoptoadNotifier' | |
| 19 | - rdoc.options << '--line-numbers' << '--inline-source' | |
| 20 | - rdoc.rdoc_files.include('README') | |
| 21 | - rdoc.rdoc_files.include('lib/**/*.rb') | |
| 22 | -end | |
| 23 | - | |
| 24 | 15 | desc 'Run ginger tests' |
| 25 | 16 | task :ginger do |
| 26 | 17 | $LOAD_PATH << File.join(*%w[vendor ginger lib]) |
| ... | ... | @@ -28,3 +19,11 @@ task :ginger do |
| 28 | 19 | ARGV << 'test' |
| 29 | 20 | load File.join(*%w[vendor ginger bin ginger]) |
| 30 | 21 | end |
| 22 | + | |
| 23 | +begin | |
| 24 | + require 'yard' | |
| 25 | + YARD::Rake::YardocTask.new do |t| | |
| 26 | + t.files = ['lib/**/*.rb', 'TESTING.rdoc'] | |
| 27 | + end | |
| 28 | +rescue LoadError | |
| 29 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,8 @@ |
| 1 | += For Maintainers: | |
| 2 | + | |
| 3 | +When developing the Hoptoad Notifier, be sure to use the integration test | |
| 4 | +against an existing project on staging before pushing to master. | |
| 5 | + | |
| 6 | ++./script/integration_test.rb <test project's api key> <staging server hostname>+ | |
| 7 | + | |
| 8 | ++./script/integration_test.rb <test project's api key> <staging server hostname> secure+ | ... | ... |
vendor/plugins/hoptoad_notifier/ginger_scenarios.rb
| ... | ... | @@ -14,8 +14,18 @@ Ginger.configure do |config| |
| 14 | 14 | config.aliases["active_support"] = "activesupport" |
| 15 | 15 | config.aliases["action_controller"] = "actionpack" |
| 16 | 16 | |
| 17 | + rails_1_2_6 = Ginger::Scenario.new | |
| 18 | + rails_1_2_6[/^active_?support$/] = "1.4.4" | |
| 19 | + rails_1_2_6[/^active_?record$/] = "1.15.6" | |
| 20 | + rails_1_2_6[/^action_?pack$/] = "1.13.6" | |
| 21 | + rails_1_2_6[/^action_?controller$/] = "1.13.6" | |
| 22 | + | |
| 23 | + config.scenarios << rails_1_2_6 | |
| 17 | 24 | config.scenarios << create_scenario("2.0.2") |
| 18 | 25 | config.scenarios << create_scenario("2.1.2") |
| 19 | 26 | config.scenarios << create_scenario("2.2.2") |
| 20 | 27 | config.scenarios << create_scenario("2.3.2") |
| 28 | + # Rails 2.3.3 has broken params filtering | |
| 29 | + # config.scenarios << create_scenario("2.3.3") | |
| 30 | + config.scenarios << create_scenario("2.3.4") | |
| 21 | 31 | end | ... | ... |
vendor/plugins/hoptoad_notifier/hoptoad_notifier.gemspec
| ... | ... | @@ -1,21 +0,0 @@ |
| 1 | -Gem::Specification.new do |s| | |
| 2 | - s.name = "hoptoad_notifier" | |
| 3 | - s.version = "1.1" | |
| 4 | - s.date = "2008-12-31" | |
| 5 | - s.summary = "Rails plugin that reports exceptions to Hoptoad." | |
| 6 | - s.email = "info@thoughtbot.com" | |
| 7 | - s.homepage = "http://github.com/thoughtbot/hoptoad_notifier" | |
| 8 | - s.description = "Rails plugin that reports exceptions to Hoptoad." | |
| 9 | - s.has_rdoc = true | |
| 10 | - s.authors = "Thoughtbot" | |
| 11 | - s.files = [ | |
| 12 | - "INSTALL", | |
| 13 | - "lib/hoptoad_notifier.rb", | |
| 14 | - "Rakefile", | |
| 15 | - "README", | |
| 16 | - "tasks/hoptoad_notifier_tasks.rake", | |
| 17 | - ] | |
| 18 | - s.test_files = [ | |
| 19 | - "test/hoptoad_notifier_test.rb" | |
| 20 | - ] | |
| 21 | -end |
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +require File.join(File.dirname(__FILE__), 'rails', 'init') | ... | ... |
vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier.rb
| ... | ... | @@ -2,365 +2,149 @@ require 'net/http' |
| 2 | 2 | require 'net/https' |
| 3 | 3 | require 'rubygems' |
| 4 | 4 | require 'active_support' |
| 5 | +require 'hoptoad_notifier/configuration' | |
| 6 | +require 'hoptoad_notifier/notice' | |
| 7 | +require 'hoptoad_notifier/sender' | |
| 8 | +require 'hoptoad_notifier/catcher' | |
| 9 | +require 'hoptoad_notifier/backtrace' | |
| 5 | 10 | |
| 6 | 11 | # Plugin for applications to automatically post errors to the Hoptoad of their choice. |
| 7 | 12 | module HoptoadNotifier |
| 8 | 13 | |
| 9 | - IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound', | |
| 10 | - 'ActionController::RoutingError', | |
| 11 | - 'ActionController::InvalidAuthenticityToken', | |
| 12 | - 'CGI::Session::CookieStore::TamperedWithCookie', | |
| 13 | - 'ActionController::UnknownAction'] | |
| 14 | + VERSION = "2.0.18" | |
| 15 | + API_VERSION = "2.0" | |
| 16 | + LOG_PREFIX = "** [Hoptoad] " | |
| 14 | 17 | |
| 15 | - # Some of these don't exist for Rails 1.2.*, so we have to consider that. | |
| 16 | - IGNORE_DEFAULT.map!{|e| eval(e) rescue nil }.compact! | |
| 17 | - IGNORE_DEFAULT.freeze | |
| 18 | - | |
| 19 | - IGNORE_USER_AGENT_DEFAULT = [] | |
| 18 | + HEADERS = { | |
| 19 | + 'Content-type' => 'text/xml', | |
| 20 | + 'Accept' => 'text/xml, application/xml' | |
| 21 | + } | |
| 20 | 22 | |
| 21 | 23 | class << self |
| 22 | - attr_accessor :host, :port, :secure, :api_key, :http_open_timeout, :http_read_timeout, | |
| 23 | - :proxy_host, :proxy_port, :proxy_user, :proxy_pass | |
| 24 | - | |
| 25 | - def backtrace_filters | |
| 26 | - @backtrace_filters ||= [] | |
| 27 | - end | |
| 28 | - | |
| 29 | - # Takes a block and adds it to the list of backtrace filters. When the filters | |
| 30 | - # run, the block will be handed each line of the backtrace and can modify | |
| 31 | - # it as necessary. For example, by default a path matching the RAILS_ROOT | |
| 32 | - # constant will be transformed into "[RAILS_ROOT]" | |
| 33 | - def filter_backtrace &block | |
| 34 | - self.backtrace_filters << block | |
| 35 | - end | |
| 36 | - | |
| 37 | - # The port on which your Hoptoad server runs. | |
| 38 | - def port | |
| 39 | - @port || (secure ? 443 : 80) | |
| 40 | - end | |
| 41 | - | |
| 42 | - # The host to connect to. | |
| 43 | - def host | |
| 44 | - @host ||= 'hoptoadapp.com' | |
| 45 | - end | |
| 46 | - | |
| 47 | - # The HTTP open timeout (defaults to 2 seconds). | |
| 48 | - def http_open_timeout | |
| 49 | - @http_open_timeout ||= 2 | |
| 50 | - end | |
| 24 | + # The sender object is responsible for delivering formatted data to the Hoptoad server. | |
| 25 | + # Must respond to #send_to_hoptoad. See HoptoadNotifier::Sender. | |
| 26 | + attr_accessor :sender | |
| 51 | 27 | |
| 52 | - # The HTTP read timeout (defaults to 5 seconds). | |
| 53 | - def http_read_timeout | |
| 54 | - @http_read_timeout ||= 5 | |
| 55 | - end | |
| 28 | + # A Hoptoad configuration object. Must act like a hash and return sensible | |
| 29 | + # values for all Hoptoad configuration options. See HoptoadNotifier::Configuration. | |
| 30 | + attr_accessor :configuration | |
| 56 | 31 | |
| 57 | - # Returns the list of errors that are being ignored. The array can be appended to. | |
| 58 | - def ignore | |
| 59 | - @ignore ||= (HoptoadNotifier::IGNORE_DEFAULT.dup) | |
| 60 | - @ignore.flatten! | |
| 61 | - @ignore | |
| 32 | + # Tell the log that the Notifier is good to go | |
| 33 | + def report_ready | |
| 34 | + write_verbose_log("Notifier #{VERSION} ready to catch errors") | |
| 62 | 35 | end |
| 63 | 36 | |
| 64 | - # Sets the list of ignored errors to only what is passed in here. This method | |
| 65 | - # can be passed a single error or a list of errors. | |
| 66 | - def ignore_only=(names) | |
| 67 | - @ignore = [names].flatten | |
| 37 | + # Prints out the environment info to the log for debugging help | |
| 38 | + def report_environment_info | |
| 39 | + write_verbose_log("Environment Info: #{environment_info}") | |
| 68 | 40 | end |
| 69 | 41 | |
| 70 | - # Returns the list of user agents that are being ignored. The array can be appended to. | |
| 71 | - def ignore_user_agent | |
| 72 | - @ignore_user_agent ||= (HoptoadNotifier::IGNORE_USER_AGENT_DEFAULT.dup) | |
| 73 | - @ignore_user_agent.flatten! | |
| 74 | - @ignore_user_agent | |
| 42 | + # Prints out the response body from Hoptoad for debugging help | |
| 43 | + def report_response_body(response) | |
| 44 | + write_verbose_log("Response from Hoptoad: \n#{response}") | |
| 75 | 45 | end |
| 76 | 46 | |
| 77 | - # Sets the list of ignored user agents to only what is passed in here. This method | |
| 78 | - # can be passed a single user agent or a list of user agents. | |
| 79 | - def ignore_user_agent_only=(names) | |
| 80 | - @ignore_user_agent = [names].flatten | |
| 47 | + # Returns the Ruby version, Rails version, and current Rails environment | |
| 48 | + def environment_info | |
| 49 | + info = "[Ruby: #{RUBY_VERSION}]" | |
| 50 | + info << " [Rails: #{::Rails::VERSION::STRING}]" if defined?(Rails) | |
| 51 | + info << " [Env: #{configuration.environment_name}]" | |
| 81 | 52 | end |
| 82 | 53 | |
| 83 | - # Returns a list of parameters that should be filtered out of what is sent to Hoptoad. | |
| 84 | - # By default, all "password" attributes will have their contents replaced. | |
| 85 | - def params_filters | |
| 86 | - @params_filters ||= %w(password) | |
| 54 | + # Writes out the given message to the #logger | |
| 55 | + def write_verbose_log(message) | |
| 56 | + logger.info LOG_PREFIX + message if logger | |
| 87 | 57 | end |
| 88 | 58 | |
| 89 | - def environment_filters | |
| 90 | - @environment_filters ||= %w() | |
| 59 | + # Look for the Rails logger currently defined | |
| 60 | + def logger | |
| 61 | + if defined?(Rails.logger) | |
| 62 | + Rails.logger | |
| 63 | + elsif defined?(RAILS_DEFAULT_LOGGER) | |
| 64 | + RAILS_DEFAULT_LOGGER | |
| 65 | + end | |
| 91 | 66 | end |
| 92 | 67 | |
| 93 | 68 | # Call this method to modify defaults in your initializers. |
| 94 | 69 | # |
| 95 | - # HoptoadNotifier.configure do |config| | |
| 96 | - # config.api_key = '1234567890abcdef' | |
| 97 | - # config.secure = false | |
| 98 | - # end | |
| 70 | + # @example | |
| 71 | + # HoptoadNotifier.configure do |config| | |
| 72 | + # config.api_key = '1234567890abcdef' | |
| 73 | + # config.secure = false | |
| 74 | + # end | |
| 75 | + def configure(silent = false) | |
| 76 | + self.configuration ||= Configuration.new | |
| 77 | + yield(configuration) | |
| 78 | + self.sender = Sender.new(configuration) | |
| 79 | + report_ready unless silent | |
| 80 | + end | |
| 81 | + | |
| 82 | + # Sends an exception manually using this method, even when you are not in a controller. | |
| 99 | 83 | # |
| 100 | - # NOTE: secure connections are not yet supported. | |
| 101 | - def configure | |
| 102 | - add_default_filters | |
| 103 | - yield self | |
| 104 | - if defined?(ActionController::Base) && !ActionController::Base.include?(HoptoadNotifier::Catcher) | |
| 105 | - ActionController::Base.send(:include, HoptoadNotifier::Catcher) | |
| 106 | - end | |
| 107 | - end | |
| 108 | - | |
| 109 | - def protocol #:nodoc: | |
| 110 | - secure ? "https" : "http" | |
| 111 | - end | |
| 112 | - | |
| 113 | - def url #:nodoc: | |
| 114 | - URI.parse("#{protocol}://#{host}:#{port}/notices/") | |
| 115 | - end | |
| 116 | - | |
| 117 | - def default_notice_options #:nodoc: | |
| 118 | - { | |
| 119 | - :api_key => HoptoadNotifier.api_key, | |
| 120 | - :error_message => 'Notification', | |
| 121 | - :backtrace => caller, | |
| 122 | - :request => {}, | |
| 123 | - :session => {}, | |
| 124 | - :environment => ENV.to_hash | |
| 125 | - } | |
| 126 | - end | |
| 127 | - | |
| 128 | - # You can send an exception manually using this method, even when you are not in a | |
| 129 | - # controller. You can pass an exception or a hash that contains the attributes that | |
| 130 | - # would be sent to Hoptoad: | |
| 131 | - # * api_key: The API key for this project. The API key is a unique identifier that Hoptoad | |
| 132 | - # uses for identification. | |
| 133 | - # * error_message: The error returned by the exception (or the message you want to log). | |
| 134 | - # * backtrace: A backtrace, usually obtained with +caller+. | |
| 135 | - # * request: The controller's request object. | |
| 136 | - # * session: The contents of the user's session. | |
| 137 | - # * environment: ENV merged with the contents of the request's environment. | |
| 138 | - def notify notice = {} | |
| 139 | - Sender.new.notify_hoptoad( notice ) | |
| 84 | + # @param [Exception] exception The exception you want to notify Hoptoad about. | |
| 85 | + # @param [Hash] opts Data that will be sent to Hoptoad. | |
| 86 | + # | |
| 87 | + # @option opts [String] :api_key The API key for this project. The API key is a unique identifier that Hoptoad uses for identification. | |
| 88 | + # @option opts [String] :error_message The error returned by the exception (or the message you want to log). | |
| 89 | + # @option opts [String] :backtrace A backtrace, usually obtained with +caller+. | |
| 90 | + # @option opts [String] :request The controller's request object. | |
| 91 | + # @option opts [String] :session The contents of the user's session. | |
| 92 | + # @option opts [String] :environment ENV merged with the contents of the request's environment. | |
| 93 | + def notify(exception, opts = {}) | |
| 94 | + send_notice(build_notice_for(exception, opts)) | |
| 140 | 95 | end |
| 141 | 96 | |
| 142 | - def add_default_filters | |
| 143 | - self.backtrace_filters.clear | |
| 144 | - | |
| 145 | - filter_backtrace do |line| | |
| 146 | - line.gsub(/#{RAILS_ROOT}/, "[RAILS_ROOT]") | |
| 147 | - end | |
| 148 | - | |
| 149 | - filter_backtrace do |line| | |
| 150 | - line.gsub(/^\.\//, "") | |
| 151 | - end | |
| 152 | - | |
| 153 | - filter_backtrace do |line| | |
| 154 | - if defined?(Gem) | |
| 155 | - Gem.path.inject(line) do |line, path| | |
| 156 | - line.gsub(/#{path}/, "[GEM_ROOT]") | |
| 157 | - end | |
| 158 | - end | |
| 159 | - end | |
| 160 | - | |
| 161 | - filter_backtrace do |line| | |
| 162 | - line if line !~ /lib\/#{File.basename(__FILE__)}/ | |
| 163 | - end | |
| 97 | + # Sends the notice unless it is one of the default ignored exceptions | |
| 98 | + # @see HoptoadNotifier.notify | |
| 99 | + def notify_or_ignore(exception, opts = {}) | |
| 100 | + notice = build_notice_for(exception, opts) | |
| 101 | + send_notice(notice) unless notice.ignore? | |
| 164 | 102 | end |
| 165 | - end | |
| 166 | 103 | |
| 167 | - # Include this module in Controllers in which you want to be notified of errors. | |
| 168 | - module Catcher | |
| 104 | + def build_lookup_hash_for(exception, options = {}) | |
| 105 | + notice = build_notice_for(exception, options) | |
| 169 | 106 | |
| 170 | - def self.included(base) #:nodoc: | |
| 171 | - if base.instance_methods.include? 'rescue_action_in_public' and !base.instance_methods.include? 'rescue_action_in_public_without_hoptoad' | |
| 172 | - base.send(:alias_method, :rescue_action_in_public_without_hoptoad, :rescue_action_in_public) | |
| 173 | - base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_hoptoad) | |
| 174 | - end | |
| 175 | - end | |
| 107 | + result = {} | |
| 108 | + result[:action] = notice.action rescue nil | |
| 109 | + result[:component] = notice.component rescue nil | |
| 110 | + result[:error_class] = notice.error_class if notice.error_class | |
| 111 | + result[:environment_name] = 'production' | |
| 176 | 112 | |
| 177 | - # Overrides the rescue_action method in ActionController::Base, but does not inhibit | |
| 178 | - # any custom processing that is defined with Rails 2's exception helpers. | |
| 179 | - def rescue_action_in_public_with_hoptoad exception | |
| 180 | - notify_hoptoad(exception) unless ignore?(exception) || ignore_user_agent? | |
| 181 | - rescue_action_in_public_without_hoptoad(exception) | |
| 182 | - end | |
| 183 | - | |
| 184 | - # This method should be used for sending manual notifications while you are still | |
| 185 | - # inside the controller. Otherwise it works like HoptoadNotifier.notify. | |
| 186 | - def notify_hoptoad hash_or_exception | |
| 187 | - if public_environment? | |
| 188 | - notice = normalize_notice(hash_or_exception) | |
| 189 | - notice = clean_notice(notice) | |
| 190 | - send_to_hoptoad(:notice => notice) | |
| 113 | + unless notice.backtrace.lines.empty? | |
| 114 | + result[:file] = notice.backtrace.lines.first.file | |
| 115 | + result[:line_number] = notice.backtrace.lines.first.number | |
| 191 | 116 | end |
| 192 | - end | |
| 193 | 117 | |
| 194 | - alias_method :inform_hoptoad, :notify_hoptoad | |
| 195 | - | |
| 196 | - # Returns the default logger or a logger that prints to STDOUT. Necessary for manual | |
| 197 | - # notifications outside of controllers. | |
| 198 | - def logger | |
| 199 | - ActiveRecord::Base.logger | |
| 200 | - rescue | |
| 201 | - @logger ||= Logger.new(STDERR) | |
| 118 | + result | |
| 202 | 119 | end |
| 203 | 120 | |
| 204 | 121 | private |
| 205 | 122 | |
| 206 | - def public_environment? #nodoc: | |
| 207 | - defined?(RAILS_ENV) and !['development', 'test'].include?(RAILS_ENV) | |
| 208 | - end | |
| 209 | - | |
| 210 | - def ignore?(exception) #:nodoc: | |
| 211 | - ignore_these = HoptoadNotifier.ignore.flatten | |
| 212 | - ignore_these.include?(exception.class) || ignore_these.include?(exception.class.name) | |
| 213 | - end | |
| 214 | - | |
| 215 | - def ignore_user_agent? #:nodoc: | |
| 216 | - HoptoadNotifier.ignore_user_agent.flatten.any? { |ua| ua === request.user_agent } | |
| 217 | - end | |
| 218 | - | |
| 219 | - def exception_to_data exception #:nodoc: | |
| 220 | - data = { | |
| 221 | - :api_key => HoptoadNotifier.api_key, | |
| 222 | - :error_class => exception.class.name, | |
| 223 | - :error_message => "#{exception.class.name}: #{exception.message}", | |
| 224 | - :backtrace => exception.backtrace, | |
| 225 | - :environment => ENV.to_hash | |
| 226 | - } | |
| 227 | - | |
| 228 | - if self.respond_to? :request | |
| 229 | - data[:request] = { | |
| 230 | - :params => request.parameters.to_hash, | |
| 231 | - :rails_root => File.expand_path(RAILS_ROOT), | |
| 232 | - :url => "#{request.protocol}#{request.host}#{request.request_uri}" | |
| 233 | - } | |
| 234 | - data[:environment].merge!(request.env.to_hash) | |
| 235 | - end | |
| 236 | - | |
| 237 | - if self.respond_to? :session | |
| 238 | - data[:session] = { | |
| 239 | - :key => session.instance_variable_get("@session_id"), | |
| 240 | - :data => session.respond_to?(:to_hash) ? | |
| 241 | - session.to_hash : | |
| 242 | - session.instance_variable_get("@data") | |
| 243 | - } | |
| 123 | + def send_notice(notice) | |
| 124 | + if configuration.public? | |
| 125 | + sender.send_to_hoptoad(notice.to_xml) | |
| 244 | 126 | end |
| 245 | - | |
| 246 | - data | |
| 247 | 127 | end |
| 248 | 128 | |
| 249 | - def normalize_notice(notice) #:nodoc: | |
| 250 | - case notice | |
| 251 | - when Hash | |
| 252 | - HoptoadNotifier.default_notice_options.merge(notice) | |
| 253 | - when Exception | |
| 254 | - HoptoadNotifier.default_notice_options.merge(exception_to_data(notice)) | |
| 255 | - end | |
| 256 | - end | |
| 257 | - | |
| 258 | - def clean_notice(notice) #:nodoc: | |
| 259 | - notice[:backtrace] = clean_hoptoad_backtrace(notice[:backtrace]) | |
| 260 | - if notice[:request].is_a?(Hash) && notice[:request][:params].is_a?(Hash) | |
| 261 | - notice[:request][:params] = filter_parameters(notice[:request][:params]) if respond_to?(:filter_parameters) | |
| 262 | - notice[:request][:params] = clean_hoptoad_params(notice[:request][:params]) | |
| 263 | - end | |
| 264 | - if notice[:environment].is_a?(Hash) | |
| 265 | - notice[:environment] = filter_parameters(notice[:environment]) if respond_to?(:filter_parameters) | |
| 266 | - notice[:environment] = clean_hoptoad_environment(notice[:environment]) | |
| 267 | - end | |
| 268 | - clean_non_serializable_data(notice) | |
| 269 | - end | |
| 270 | - | |
| 271 | - def send_to_hoptoad data #:nodoc: | |
| 272 | - headers = { | |
| 273 | - 'Content-type' => 'application/x-yaml', | |
| 274 | - 'Accept' => 'text/xml, application/xml' | |
| 275 | - } | |
| 276 | - | |
| 277 | - url = HoptoadNotifier.url | |
| 278 | - | |
| 279 | - http = Net::HTTP::Proxy(HoptoadNotifier.proxy_host, | |
| 280 | - HoptoadNotifier.proxy_port, | |
| 281 | - HoptoadNotifier.proxy_user, | |
| 282 | - HoptoadNotifier.proxy_pass).new(url.host, url.port) | |
| 283 | - | |
| 284 | - http.use_ssl = true | |
| 285 | - http.read_timeout = HoptoadNotifier.http_read_timeout | |
| 286 | - http.open_timeout = HoptoadNotifier.http_open_timeout | |
| 287 | - http.use_ssl = !!HoptoadNotifier.secure | |
| 288 | - | |
| 289 | - response = begin | |
| 290 | - http.post(url.path, stringify_keys(data).to_yaml, headers) | |
| 291 | - rescue TimeoutError => e | |
| 292 | - logger.error "Timeout while contacting the Hoptoad server." if logger | |
| 293 | - nil | |
| 294 | - end | |
| 295 | - | |
| 296 | - case response | |
| 297 | - when Net::HTTPSuccess then | |
| 298 | - logger.info "Hoptoad Success: #{response.class}" if logger | |
| 129 | + def build_notice_for(exception, opts = {}) | |
| 130 | + exception = unwrap_exception(exception) | |
| 131 | + if exception.respond_to?(:to_hash) | |
| 132 | + opts = opts.merge(exception) | |
| 299 | 133 | else |
| 300 | - logger.error "Hoptoad Failure: #{response.class}\n#{response.body if response.respond_to? :body}" if logger | |
| 301 | - end | |
| 302 | - end | |
| 303 | - | |
| 304 | - def clean_hoptoad_backtrace backtrace #:nodoc: | |
| 305 | - if backtrace.to_a.size == 1 | |
| 306 | - backtrace = backtrace.to_a.first.split(/\n\s*/) | |
| 307 | - end | |
| 308 | - | |
| 309 | - filtered = backtrace.to_a.map do |line| | |
| 310 | - HoptoadNotifier.backtrace_filters.inject(line) do |line, proc| | |
| 311 | - proc.call(line) | |
| 312 | - end | |
| 134 | + opts = opts.merge(:exception => exception) | |
| 313 | 135 | end |
| 314 | - | |
| 315 | - filtered.compact | |
| 136 | + Notice.new(configuration.merge(opts)) | |
| 316 | 137 | end |
| 317 | 138 | |
| 318 | - def clean_hoptoad_params params #:nodoc: | |
| 319 | - params.each do |k, v| | |
| 320 | - params[k] = "[FILTERED]" if HoptoadNotifier.params_filters.any? do |filter| | |
| 321 | - k.to_s.match(/#{filter}/) | |
| 322 | - end | |
| 323 | - end | |
| 324 | - end | |
| 325 | - | |
| 326 | - def clean_hoptoad_environment env #:nodoc: | |
| 327 | - env.each do |k, v| | |
| 328 | - env[k] = "[FILTERED]" if HoptoadNotifier.environment_filters.any? do |filter| | |
| 329 | - k.to_s.match(/#{filter}/) | |
| 330 | - end | |
| 331 | - end | |
| 332 | - end | |
| 333 | - | |
| 334 | - def clean_non_serializable_data(notice) #:nodoc: | |
| 335 | - notice.select{|k,v| serializable?(v) }.inject({}) do |h, pair| | |
| 336 | - h[pair.first] = pair.last.is_a?(Hash) ? clean_non_serializable_data(pair.last) : pair.last | |
| 337 | - h | |
| 338 | - end | |
| 339 | - end | |
| 340 | - | |
| 341 | - def serializable?(value) #:nodoc: | |
| 342 | - value.is_a?(Fixnum) || | |
| 343 | - value.is_a?(Array) || | |
| 344 | - value.is_a?(String) || | |
| 345 | - value.is_a?(Hash) || | |
| 346 | - value.is_a?(Bignum) | |
| 347 | - end | |
| 348 | - | |
| 349 | - def stringify_keys(hash) #:nodoc: | |
| 350 | - hash.inject({}) do |h, pair| | |
| 351 | - h[pair.first.to_s] = pair.last.is_a?(Hash) ? stringify_keys(pair.last) : pair.last | |
| 352 | - h | |
| 139 | + def unwrap_exception(exception) | |
| 140 | + if exception.respond_to?(:original_exception) | |
| 141 | + exception.original_exception | |
| 142 | + elsif exception.respond_to?(:continued_exception) | |
| 143 | + exception.continued_exception | |
| 144 | + else | |
| 145 | + exception | |
| 353 | 146 | end |
| 354 | 147 | end |
| 355 | - | |
| 356 | - end | |
| 357 | - | |
| 358 | - # A dummy class for sending notifications manually outside of a controller. | |
| 359 | - class Sender | |
| 360 | - def rescue_action_in_public(exception) | |
| 361 | - end | |
| 362 | - | |
| 363 | - include HoptoadNotifier::Catcher | |
| 364 | 148 | end |
| 365 | 149 | end |
| 366 | 150 | ... | ... |
vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/backtrace.rb
0 → 100644
| ... | ... | @@ -0,0 +1,99 @@ |
| 1 | +module HoptoadNotifier | |
| 2 | + # Front end to parsing the backtrace for each notice | |
| 3 | + class Backtrace | |
| 4 | + | |
| 5 | + # Handles backtrace parsing line by line | |
| 6 | + class Line | |
| 7 | + | |
| 8 | + INPUT_FORMAT = %r{^([^:]+):(\d+)(?::in `([^']+)')?$}.freeze | |
| 9 | + | |
| 10 | + # The file portion of the line (such as app/models/user.rb) | |
| 11 | + attr_reader :file | |
| 12 | + | |
| 13 | + # The line number portion of the line | |
| 14 | + attr_reader :number | |
| 15 | + | |
| 16 | + # The method of the line (such as index) | |
| 17 | + attr_reader :method | |
| 18 | + | |
| 19 | + # Parses a single line of a given backtrace | |
| 20 | + # @param [String] unparsed_line The raw line from +caller+ or some backtrace | |
| 21 | + # @return [Line] The parsed backtrace line | |
| 22 | + def self.parse(unparsed_line) | |
| 23 | + _, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a | |
| 24 | + new(file, number, method) | |
| 25 | + end | |
| 26 | + | |
| 27 | + def initialize(file, number, method) | |
| 28 | + self.file = file | |
| 29 | + self.number = number | |
| 30 | + self.method = method | |
| 31 | + end | |
| 32 | + | |
| 33 | + # Reconstructs the line in a readable fashion | |
| 34 | + def to_s | |
| 35 | + "#{file}:#{number}:in `#{method}'" | |
| 36 | + end | |
| 37 | + | |
| 38 | + def ==(other) | |
| 39 | + to_s == other.to_s | |
| 40 | + end | |
| 41 | + | |
| 42 | + def inspect | |
| 43 | + "<Line:#{to_s}>" | |
| 44 | + end | |
| 45 | + | |
| 46 | + private | |
| 47 | + | |
| 48 | + attr_writer :file, :number, :method | |
| 49 | + end | |
| 50 | + | |
| 51 | + # holder for an Array of Backtrace::Line instances | |
| 52 | + attr_reader :lines | |
| 53 | + | |
| 54 | + def self.parse(ruby_backtrace, opts = {}) | |
| 55 | + ruby_lines = split_multiline_backtrace(ruby_backtrace) | |
| 56 | + | |
| 57 | + filters = opts[:filters] || [] | |
| 58 | + filtered_lines = ruby_lines.to_a.map do |line| | |
| 59 | + filters.inject(line) do |line, proc| | |
| 60 | + proc.call(line) | |
| 61 | + end | |
| 62 | + end.compact | |
| 63 | + | |
| 64 | + lines = filtered_lines.collect do |unparsed_line| | |
| 65 | + Line.parse(unparsed_line) | |
| 66 | + end | |
| 67 | + | |
| 68 | + instance = new(lines) | |
| 69 | + end | |
| 70 | + | |
| 71 | + def initialize(lines) | |
| 72 | + self.lines = lines | |
| 73 | + end | |
| 74 | + | |
| 75 | + def inspect | |
| 76 | + "<Backtrace: " + lines.collect { |line| line.inspect }.join(", ") + ">" | |
| 77 | + end | |
| 78 | + | |
| 79 | + def ==(other) | |
| 80 | + if other.respond_to?(:lines) | |
| 81 | + lines == other.lines | |
| 82 | + else | |
| 83 | + false | |
| 84 | + end | |
| 85 | + end | |
| 86 | + | |
| 87 | + private | |
| 88 | + | |
| 89 | + attr_writer :lines | |
| 90 | + | |
| 91 | + def self.split_multiline_backtrace(backtrace) | |
| 92 | + if backtrace.to_a.size == 1 | |
| 93 | + backtrace.to_a.first.split(/\n\s*/) | |
| 94 | + else | |
| 95 | + backtrace | |
| 96 | + end | |
| 97 | + end | |
| 98 | + end | |
| 99 | +end | ... | ... |
vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/catcher.rb
0 → 100644
| ... | ... | @@ -0,0 +1,95 @@ |
| 1 | +module HoptoadNotifier | |
| 2 | + # Include this module in Controllers in which you want to be notified of errors. | |
| 3 | + module Catcher | |
| 4 | + | |
| 5 | + # Sets up an alias chain to catch exceptions when Rails does | |
| 6 | + def self.included(base) #:nodoc: | |
| 7 | + if base.instance_methods.map(&:to_s).include? 'rescue_action_in_public' and !base.instance_methods.map(&:to_s).include? 'rescue_action_in_public_without_hoptoad' | |
| 8 | + base.send(:alias_method, :rescue_action_in_public_without_hoptoad, :rescue_action_in_public) | |
| 9 | + base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_hoptoad) | |
| 10 | + base.send(:alias_method, :rescue_action_locally_without_hoptoad, :rescue_action_locally) | |
| 11 | + base.send(:alias_method, :rescue_action_locally, :rescue_action_locally_with_hoptoad) | |
| 12 | + end | |
| 13 | + end | |
| 14 | + | |
| 15 | + private | |
| 16 | + | |
| 17 | + # Overrides the rescue_action method in ActionController::Base, but does not inhibit | |
| 18 | + # any custom processing that is defined with Rails 2's exception helpers. | |
| 19 | + def rescue_action_in_public_with_hoptoad(exception) | |
| 20 | + unless hoptoad_ignore_user_agent? | |
| 21 | + HoptoadNotifier.notify_or_ignore(exception, hoptoad_request_data) | |
| 22 | + end | |
| 23 | + rescue_action_in_public_without_hoptoad(exception) | |
| 24 | + end | |
| 25 | + | |
| 26 | + def rescue_action_locally_with_hoptoad(exception) | |
| 27 | + result = rescue_action_locally_without_hoptoad(exception) | |
| 28 | + | |
| 29 | + if HoptoadNotifier.configuration.development_lookup | |
| 30 | + path = File.join(File.dirname(__FILE__), '..', 'templates', 'rescue.erb') | |
| 31 | + notice = HoptoadNotifier.build_lookup_hash_for(exception, hoptoad_request_data) | |
| 32 | + | |
| 33 | + result << @template.render( | |
| 34 | + :file => path, | |
| 35 | + :use_full_path => false, | |
| 36 | + :locals => { :host => HoptoadNotifier.configuration.host, | |
| 37 | + :api_key => HoptoadNotifier.configuration.api_key, | |
| 38 | + :notice => notice }) | |
| 39 | + end | |
| 40 | + | |
| 41 | + result | |
| 42 | + end | |
| 43 | + | |
| 44 | + # This method should be used for sending manual notifications while you are still | |
| 45 | + # inside the controller. Otherwise it works like HoptoadNotifier.notify. | |
| 46 | + def notify_hoptoad(hash_or_exception) | |
| 47 | + unless consider_all_requests_local || local_request? | |
| 48 | + HoptoadNotifier.notify(hash_or_exception, hoptoad_request_data) | |
| 49 | + end | |
| 50 | + end | |
| 51 | + | |
| 52 | + def hoptoad_ignore_user_agent? #:nodoc: | |
| 53 | + # Rails 1.2.6 doesn't have request.user_agent, so check for it here | |
| 54 | + user_agent = request.respond_to?(:user_agent) ? request.user_agent : request.env["HTTP_USER_AGENT"] | |
| 55 | + HoptoadNotifier.configuration.ignore_user_agent.flatten.any? { |ua| ua === user_agent } | |
| 56 | + end | |
| 57 | + | |
| 58 | + def hoptoad_request_data | |
| 59 | + { :parameters => hoptoad_filter_if_filtering(params.to_hash), | |
| 60 | + :session_data => hoptoad_session_data, | |
| 61 | + :controller => params[:controller], | |
| 62 | + :action => params[:action], | |
| 63 | + :url => hoptoad_request_url, | |
| 64 | + :cgi_data => hoptoad_filter_if_filtering(request.env), | |
| 65 | + :environment_vars => hoptoad_filter_if_filtering(ENV) } | |
| 66 | + end | |
| 67 | + | |
| 68 | + def hoptoad_filter_if_filtering(hash) | |
| 69 | + if respond_to?(:filter_parameters) | |
| 70 | + filter_parameters(hash) rescue hash | |
| 71 | + else | |
| 72 | + hash | |
| 73 | + end | |
| 74 | + end | |
| 75 | + | |
| 76 | + def hoptoad_session_data | |
| 77 | + if session.respond_to?(:to_hash) | |
| 78 | + session.to_hash | |
| 79 | + else | |
| 80 | + session.data | |
| 81 | + end | |
| 82 | + end | |
| 83 | + | |
| 84 | + def hoptoad_request_url | |
| 85 | + url = "#{request.protocol}#{request.host}" | |
| 86 | + | |
| 87 | + unless [80, 443].include?(request.port) | |
| 88 | + url << ":#{request.port}" | |
| 89 | + end | |
| 90 | + | |
| 91 | + url << request.request_uri | |
| 92 | + url | |
| 93 | + end | |
| 94 | + end | |
| 95 | +end | ... | ... |
vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/configuration.rb
0 → 100644
| ... | ... | @@ -0,0 +1,229 @@ |
| 1 | +module HoptoadNotifier | |
| 2 | + # Used to set up and modify settings for the notifier. | |
| 3 | + class Configuration | |
| 4 | + | |
| 5 | + OPTIONS = [:api_key, :backtrace_filters, :development_environments, | |
| 6 | + :development_lookup, :environment_name, :host, | |
| 7 | + :http_open_timeout, :http_read_timeout, :ignore, :ignore_by_filters, | |
| 8 | + :ignore_user_agent, :notifier_name, :notifier_url, :notifier_version, | |
| 9 | + :params_filters, :project_root, :port, :protocol, :proxy_host, | |
| 10 | + :proxy_pass, :proxy_port, :proxy_user, :secure].freeze | |
| 11 | + | |
| 12 | + # The API key for your project, found on the project edit form. | |
| 13 | + attr_accessor :api_key | |
| 14 | + | |
| 15 | + # The host to connect to (defaults to hoptoadapp.com). | |
| 16 | + attr_accessor :host | |
| 17 | + | |
| 18 | + # The port on which your Hoptoad server runs (defaults to 443 for secure | |
| 19 | + # connections, 80 for insecure connections). | |
| 20 | + attr_accessor :port | |
| 21 | + | |
| 22 | + # +true+ for https connections, +false+ for http connections. | |
| 23 | + attr_accessor :secure | |
| 24 | + | |
| 25 | + # The HTTP open timeout in seconds (defaults to 2). | |
| 26 | + attr_accessor :http_open_timeout | |
| 27 | + | |
| 28 | + # The HTTP read timeout in seconds (defaults to 5). | |
| 29 | + attr_accessor :http_read_timeout | |
| 30 | + | |
| 31 | + # The hostname of your proxy server (if using a proxy) | |
| 32 | + attr_accessor :proxy_host | |
| 33 | + | |
| 34 | + # The port of your proxy server (if using a proxy) | |
| 35 | + attr_accessor :proxy_port | |
| 36 | + | |
| 37 | + # The username to use when logging into your proxy server (if using a proxy) | |
| 38 | + attr_accessor :proxy_user | |
| 39 | + | |
| 40 | + # The password to use when logging into your proxy server (if using a proxy) | |
| 41 | + attr_accessor :proxy_pass | |
| 42 | + | |
| 43 | + # A list of parameters that should be filtered out of what is sent to Hoptoad. | |
| 44 | + # By default, all "password" attributes will have their contents replaced. | |
| 45 | + attr_reader :params_filters | |
| 46 | + | |
| 47 | + # A list of filters for cleaning and pruning the backtrace. See #filter_backtrace. | |
| 48 | + attr_reader :backtrace_filters | |
| 49 | + | |
| 50 | + # A list of filters for ignoring exceptions. See #ignore_by_filter. | |
| 51 | + attr_reader :ignore_by_filters | |
| 52 | + | |
| 53 | + # A list of exception classes to ignore. The array can be appended to. | |
| 54 | + attr_reader :ignore | |
| 55 | + | |
| 56 | + # A list of user agents that are being ignored. The array can be appended to. | |
| 57 | + attr_reader :ignore_user_agent | |
| 58 | + | |
| 59 | + # A list of environments in which notifications should not be sent. | |
| 60 | + attr_accessor :development_environments | |
| 61 | + | |
| 62 | + # +true+ if you want to check for production errors matching development errors, +false+ otherwise. | |
| 63 | + attr_accessor :development_lookup | |
| 64 | + | |
| 65 | + # The name of the environment the application is running in | |
| 66 | + attr_accessor :environment_name | |
| 67 | + | |
| 68 | + # The path to the project in which the error occurred, such as the RAILS_ROOT | |
| 69 | + attr_accessor :project_root | |
| 70 | + | |
| 71 | + # The name of the notifier library being used to send notifications (such as "Hoptoad Notifier") | |
| 72 | + attr_accessor :notifier_name | |
| 73 | + | |
| 74 | + # The version of the notifier library being used to send notifications (such as "1.0.2") | |
| 75 | + attr_accessor :notifier_version | |
| 76 | + | |
| 77 | + # The url of the notifier library being used to send notifications | |
| 78 | + attr_accessor :notifier_url | |
| 79 | + | |
| 80 | + DEFAULT_PARAMS_FILTERS = %w(password password_confirmation).freeze | |
| 81 | + | |
| 82 | + DEFAULT_BACKTRACE_FILTERS = [ | |
| 83 | + lambda { |line| | |
| 84 | + if defined?(HoptoadNotifier.configuration.project_root) | |
| 85 | + line.gsub(/#{HoptoadNotifier.configuration.project_root}/, "[PROJECT_ROOT]") | |
| 86 | + else | |
| 87 | + line | |
| 88 | + end | |
| 89 | + }, | |
| 90 | + lambda { |line| line.gsub(/^\.\//, "") }, | |
| 91 | + lambda { |line| | |
| 92 | + if defined?(Gem) | |
| 93 | + Gem.path.inject(line) do |line, path| | |
| 94 | + line.gsub(/#{path}/, "[GEM_ROOT]") | |
| 95 | + end | |
| 96 | + end | |
| 97 | + }, | |
| 98 | + lambda { |line| line if line !~ %r{lib/hoptoad_notifier} } | |
| 99 | + ].freeze | |
| 100 | + | |
| 101 | + IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound', | |
| 102 | + 'ActionController::RoutingError', | |
| 103 | + 'ActionController::InvalidAuthenticityToken', | |
| 104 | + 'CGI::Session::CookieStore::TamperedWithCookie', | |
| 105 | + 'ActionController::UnknownAction'] | |
| 106 | + | |
| 107 | + # Some of these don't exist for Rails 1.2.*, so we have to consider that. | |
| 108 | + IGNORE_DEFAULT.map!{|e| eval(e) rescue nil }.compact! | |
| 109 | + IGNORE_DEFAULT.freeze | |
| 110 | + | |
| 111 | + alias_method :secure?, :secure | |
| 112 | + | |
| 113 | + def initialize | |
| 114 | + @secure = false | |
| 115 | + @host = 'hoptoadapp.com' | |
| 116 | + @http_open_timeout = 2 | |
| 117 | + @http_read_timeout = 5 | |
| 118 | + @params_filters = DEFAULT_PARAMS_FILTERS.dup | |
| 119 | + @backtrace_filters = DEFAULT_BACKTRACE_FILTERS.dup | |
| 120 | + @ignore_by_filters = [] | |
| 121 | + @ignore = IGNORE_DEFAULT.dup | |
| 122 | + @ignore_user_agent = [] | |
| 123 | + @development_environments = %w(development test cucumber) | |
| 124 | + @development_lookup = true | |
| 125 | + @notifier_name = 'Hoptoad Notifier' | |
| 126 | + @notifier_version = VERSION | |
| 127 | + @notifier_url = 'http://hoptoadapp.com' | |
| 128 | + end | |
| 129 | + | |
| 130 | + # Takes a block and adds it to the list of backtrace filters. When the filters | |
| 131 | + # run, the block will be handed each line of the backtrace and can modify | |
| 132 | + # it as necessary. | |
| 133 | + # | |
| 134 | + # @example | |
| 135 | + # config.filter_bracktrace do |line| | |
| 136 | + # line.gsub(/^#{Rails.root}/, "[RAILS_ROOT]") | |
| 137 | + # end | |
| 138 | + # | |
| 139 | + # @param [Proc] block The new backtrace filter. | |
| 140 | + # @yieldparam [String] line A line in the backtrace. | |
| 141 | + def filter_backtrace(&block) | |
| 142 | + self.backtrace_filters << block | |
| 143 | + end | |
| 144 | + | |
| 145 | + # Takes a block and adds it to the list of ignore filters. | |
| 146 | + # When the filters run, the block will be handed the exception. | |
| 147 | + # @example | |
| 148 | + # config.ignore_by_filter do |exception_data| | |
| 149 | + # true if exception_data[:error_class] == "RuntimeError" | |
| 150 | + # end | |
| 151 | + # | |
| 152 | + # @param [Proc] block The new ignore filter | |
| 153 | + # @yieldparam [Hash] data The exception data given to +HoptoadNotifier.notify+ | |
| 154 | + # @yieldreturn [Boolean] If the block returns true the exception will be ignored, otherwise it will be processed by hoptoad. | |
| 155 | + def ignore_by_filter(&block) | |
| 156 | + self.ignore_by_filters << block | |
| 157 | + end | |
| 158 | + | |
| 159 | + # Overrides the list of default ignored errors. | |
| 160 | + # | |
| 161 | + # @param [Array<Exception>] names A list of exceptions to ignore. | |
| 162 | + def ignore_only=(names) | |
| 163 | + @ignore = [names].flatten | |
| 164 | + end | |
| 165 | + | |
| 166 | + # Overrides the list of default ignored user agents | |
| 167 | + # | |
| 168 | + # @param [Array<String>] A list of user agents to ignore | |
| 169 | + def ignore_user_agent_only=(names) | |
| 170 | + @ignore_user_agent = [names].flatten | |
| 171 | + end | |
| 172 | + | |
| 173 | + # Allows config options to be read like a hash | |
| 174 | + # | |
| 175 | + # @param [Symbol] option Key for a given attribute | |
| 176 | + def [](option) | |
| 177 | + send(option) | |
| 178 | + end | |
| 179 | + | |
| 180 | + # Returns a hash of all configurable options | |
| 181 | + def to_hash | |
| 182 | + OPTIONS.inject({}) do |hash, option| | |
| 183 | + hash.merge(option.to_sym => send(option)) | |
| 184 | + end | |
| 185 | + end | |
| 186 | + | |
| 187 | + # Returns a hash of all configurable options merged with +hash+ | |
| 188 | + # | |
| 189 | + # @param [Hash] hash A set of configuration options that will take precedence over the defaults | |
| 190 | + def merge(hash) | |
| 191 | + to_hash.merge(hash) | |
| 192 | + end | |
| 193 | + | |
| 194 | + # Determines if the notifier will send notices. | |
| 195 | + # @return [Boolean] Returns +false+ if in a development environment, +true+ otherwise. | |
| 196 | + def public? | |
| 197 | + !development_environments.include?(environment_name) | |
| 198 | + end | |
| 199 | + | |
| 200 | + def port | |
| 201 | + @port || default_port | |
| 202 | + end | |
| 203 | + | |
| 204 | + def protocol | |
| 205 | + if secure? | |
| 206 | + 'https' | |
| 207 | + else | |
| 208 | + 'http' | |
| 209 | + end | |
| 210 | + end | |
| 211 | + | |
| 212 | + def environment_filters | |
| 213 | + warn 'config.environment_filters has been deprecated and has no effect.' | |
| 214 | + [] | |
| 215 | + end | |
| 216 | + | |
| 217 | + private | |
| 218 | + | |
| 219 | + def default_port | |
| 220 | + if secure? | |
| 221 | + 443 | |
| 222 | + else | |
| 223 | + 80 | |
| 224 | + end | |
| 225 | + end | |
| 226 | + | |
| 227 | + end | |
| 228 | + | |
| 229 | +end | ... | ... |
vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/notice.rb
0 → 100644
| ... | ... | @@ -0,0 +1,295 @@ |
| 1 | +module HoptoadNotifier | |
| 2 | + class Notice | |
| 3 | + | |
| 4 | + # The exception that caused this notice, if any | |
| 5 | + attr_reader :exception | |
| 6 | + | |
| 7 | + # The API key for the project to which this notice should be sent | |
| 8 | + attr_reader :api_key | |
| 9 | + | |
| 10 | + # The backtrace from the given exception or hash. | |
| 11 | + attr_reader :backtrace | |
| 12 | + | |
| 13 | + # The name of the class of error (such as RuntimeError) | |
| 14 | + attr_reader :error_class | |
| 15 | + | |
| 16 | + # The name of the server environment (such as "production") | |
| 17 | + attr_reader :environment_name | |
| 18 | + | |
| 19 | + # CGI variables such as HTTP_METHOD | |
| 20 | + attr_reader :cgi_data | |
| 21 | + | |
| 22 | + # The message from the exception, or a general description of the error | |
| 23 | + attr_reader :error_message | |
| 24 | + | |
| 25 | + # See Configuration#backtrace_filters | |
| 26 | + attr_reader :backtrace_filters | |
| 27 | + | |
| 28 | + # See Configuration#params_filters | |
| 29 | + attr_reader :params_filters | |
| 30 | + | |
| 31 | + # A hash of parameters from the query string or post body. | |
| 32 | + attr_reader :parameters | |
| 33 | + alias_method :params, :parameters | |
| 34 | + | |
| 35 | + # The component (if any) which was used in this request (usually the controller) | |
| 36 | + attr_reader :component | |
| 37 | + alias_method :controller, :component | |
| 38 | + | |
| 39 | + # The action (if any) that was called in this request | |
| 40 | + attr_reader :action | |
| 41 | + | |
| 42 | + # A hash of session data from the request | |
| 43 | + attr_reader :session_data | |
| 44 | + | |
| 45 | + # The path to the project that caused the error (usually RAILS_ROOT) | |
| 46 | + attr_reader :project_root | |
| 47 | + | |
| 48 | + # The URL at which the error occurred (if any) | |
| 49 | + attr_reader :url | |
| 50 | + | |
| 51 | + # See Configuration#ignore | |
| 52 | + attr_reader :ignore | |
| 53 | + | |
| 54 | + # See Configuration#ignore_by_filters | |
| 55 | + attr_reader :ignore_by_filters | |
| 56 | + | |
| 57 | + # The name of the notifier library sending this notice, such as "Hoptoad Notifier" | |
| 58 | + attr_reader :notifier_name | |
| 59 | + | |
| 60 | + # The version number of the notifier library sending this notice, such as "2.1.3" | |
| 61 | + attr_reader :notifier_version | |
| 62 | + | |
| 63 | + # A URL for more information about the notifier library sending this notice | |
| 64 | + attr_reader :notifier_url | |
| 65 | + | |
| 66 | + def initialize(args) | |
| 67 | + self.args = args | |
| 68 | + self.exception = args[:exception] | |
| 69 | + self.api_key = args[:api_key] | |
| 70 | + self.project_root = args[:project_root] | |
| 71 | + self.url = args[:url] | |
| 72 | + | |
| 73 | + self.notifier_name = args[:notifier_name] | |
| 74 | + self.notifier_version = args[:notifier_version] | |
| 75 | + self.notifier_url = args[:notifier_url] | |
| 76 | + | |
| 77 | + self.ignore = args[:ignore] || [] | |
| 78 | + self.ignore_by_filters = args[:ignore_by_filters] || [] | |
| 79 | + self.backtrace_filters = args[:backtrace_filters] || [] | |
| 80 | + self.params_filters = args[:params_filters] || [] | |
| 81 | + self.parameters = args[:parameters] || {} | |
| 82 | + self.component = args[:component] || args[:controller] | |
| 83 | + self.action = args[:action] | |
| 84 | + | |
| 85 | + self.environment_name = args[:environment_name] | |
| 86 | + self.cgi_data = args[:cgi_data] | |
| 87 | + self.backtrace = Backtrace.parse(exception_attribute(:backtrace, caller)) | |
| 88 | + self.error_class = exception_attribute(:error_class) {|exception| exception.class.name } | |
| 89 | + self.error_message = exception_attribute(:error_message, 'Notification') do |exception| | |
| 90 | + "#{exception.class.name}: #{exception.message}" | |
| 91 | + end | |
| 92 | + | |
| 93 | + find_session_data | |
| 94 | + clean_params | |
| 95 | + end | |
| 96 | + | |
| 97 | + # Converts the given notice to XML | |
| 98 | + def to_xml | |
| 99 | + builder = Builder::XmlMarkup.new | |
| 100 | + builder.instruct! | |
| 101 | + xml = builder.notice(:version => HoptoadNotifier::API_VERSION) do |notice| | |
| 102 | + notice.tag!("api-key", api_key) | |
| 103 | + notice.notifier do |notifier| | |
| 104 | + notifier.name(notifier_name) | |
| 105 | + notifier.version(notifier_version) | |
| 106 | + notifier.url(notifier_url) | |
| 107 | + end | |
| 108 | + notice.error do |error| | |
| 109 | + error.tag!('class', error_class) | |
| 110 | + error.message(error_message) | |
| 111 | + error.backtrace do |backtrace| | |
| 112 | + self.backtrace.lines.each do |line| | |
| 113 | + backtrace.line(:number => line.number, | |
| 114 | + :file => line.file, | |
| 115 | + :method => line.method) | |
| 116 | + end | |
| 117 | + end | |
| 118 | + end | |
| 119 | + if url || | |
| 120 | + controller || | |
| 121 | + action || | |
| 122 | + !parameters.blank? || | |
| 123 | + !cgi_data.blank? || | |
| 124 | + !session_data.blank? | |
| 125 | + notice.request do |request| | |
| 126 | + request.url(url) | |
| 127 | + request.component(controller) | |
| 128 | + request.action(action) | |
| 129 | + unless parameters.blank? | |
| 130 | + request.params do |params| | |
| 131 | + xml_vars_for(params, parameters) | |
| 132 | + end | |
| 133 | + end | |
| 134 | + unless session_data.blank? | |
| 135 | + request.session do |session| | |
| 136 | + xml_vars_for(session, session_data) | |
| 137 | + end | |
| 138 | + end | |
| 139 | + unless cgi_data.blank? | |
| 140 | + request.tag!("cgi-data") do |cgi_datum| | |
| 141 | + xml_vars_for(cgi_datum, cgi_data) | |
| 142 | + end | |
| 143 | + end | |
| 144 | + end | |
| 145 | + end | |
| 146 | + notice.tag!("server-environment") do |env| | |
| 147 | + env.tag!("project-root", project_root) | |
| 148 | + env.tag!("environment-name", environment_name) | |
| 149 | + end | |
| 150 | + end | |
| 151 | + xml.to_s | |
| 152 | + end | |
| 153 | + | |
| 154 | + # Determines if this notice should be ignored | |
| 155 | + def ignore? | |
| 156 | + ignored_class_names.include?(error_class) || | |
| 157 | + ignore_by_filters.any? {|filter| filter.call(self) } | |
| 158 | + end | |
| 159 | + | |
| 160 | + # Allows properties to be accessed using a hash-like syntax | |
| 161 | + # | |
| 162 | + # @example | |
| 163 | + # notice[:error_message] | |
| 164 | + # @param [String] method The given key for an attribute | |
| 165 | + # @return The attribute value, or self if given +:request+ | |
| 166 | + def [](method) | |
| 167 | + case method | |
| 168 | + when :request | |
| 169 | + self | |
| 170 | + else | |
| 171 | + send(method) | |
| 172 | + end | |
| 173 | + end | |
| 174 | + | |
| 175 | + private | |
| 176 | + | |
| 177 | + attr_writer :exception, :api_key, :backtrace, :error_class, :error_message, | |
| 178 | + :backtrace_filters, :parameters, :params_filters, | |
| 179 | + :environment_filters, :session_data, :project_root, :url, :ignore, | |
| 180 | + :ignore_by_filters, :notifier_name, :notifier_url, :notifier_version, | |
| 181 | + :component, :action, :cgi_data, :environment_name | |
| 182 | + | |
| 183 | + # Arguments given in the initializer | |
| 184 | + attr_accessor :args | |
| 185 | + | |
| 186 | + # Gets a property named +attribute+ of an exception, either from an actual | |
| 187 | + # exception or a hash. | |
| 188 | + # | |
| 189 | + # If an exception is available, #from_exception will be used. Otherwise, | |
| 190 | + # a key named +attribute+ will be used from the #args. | |
| 191 | + # | |
| 192 | + # If no exception or hash key is available, +default+ will be used. | |
| 193 | + def exception_attribute(attribute, default = nil, &block) | |
| 194 | + (exception && from_exception(attribute, &block)) || args[attribute] || default | |
| 195 | + end | |
| 196 | + | |
| 197 | + # Gets a property named +attribute+ from an exception. | |
| 198 | + # | |
| 199 | + # If a block is given, it will be used when getting the property from an | |
| 200 | + # exception. The block should accept and exception and return the value for | |
| 201 | + # the property. | |
| 202 | + # | |
| 203 | + # If no block is given, a method with the same name as +attribute+ will be | |
| 204 | + # invoked for the value. | |
| 205 | + def from_exception(attribute) | |
| 206 | + if block_given? | |
| 207 | + yield(exception) | |
| 208 | + else | |
| 209 | + exception.send(attribute) | |
| 210 | + end | |
| 211 | + end | |
| 212 | + | |
| 213 | + # Removes non-serializable data from the given attribute. | |
| 214 | + # See #clean_unserializable_data | |
| 215 | + def clean_unserializable_data_from(attribute) | |
| 216 | + self.send(:"#{attribute}=", clean_unserializable_data(send(attribute))) | |
| 217 | + end | |
| 218 | + | |
| 219 | + # Removes non-serializable data. Allowed data types are strings, arrays, | |
| 220 | + # and hashes. All other types are converted to strings. | |
| 221 | + # TODO: move this onto Hash | |
| 222 | + def clean_unserializable_data(data) | |
| 223 | + if data.respond_to?(:to_hash) | |
| 224 | + data.to_hash.inject({}) do |result, (key, value)| | |
| 225 | + result.merge(key => clean_unserializable_data(value)) | |
| 226 | + end | |
| 227 | + elsif data.respond_to?(:to_ary) | |
| 228 | + data.collect do |value| | |
| 229 | + clean_unserializable_data(value) | |
| 230 | + end | |
| 231 | + else | |
| 232 | + data.to_s | |
| 233 | + end | |
| 234 | + end | |
| 235 | + | |
| 236 | + # Replaces the contents of params that match params_filters. | |
| 237 | + # TODO: extract this to a different class | |
| 238 | + def clean_params | |
| 239 | + clean_unserializable_data_from(:parameters) | |
| 240 | + filter(parameters) | |
| 241 | + if cgi_data | |
| 242 | + clean_unserializable_data_from(:cgi_data) | |
| 243 | + filter(cgi_data) | |
| 244 | + end | |
| 245 | + if session_data | |
| 246 | + clean_unserializable_data_from(:session_data) | |
| 247 | + end | |
| 248 | + end | |
| 249 | + | |
| 250 | + def filter(hash) | |
| 251 | + if params_filters | |
| 252 | + hash.each do |key, value| | |
| 253 | + if filter_key?(key) | |
| 254 | + hash[key] = "[FILTERED]" | |
| 255 | + elsif value.respond_to?(:to_hash) | |
| 256 | + filter(hash[key]) | |
| 257 | + end | |
| 258 | + end | |
| 259 | + end | |
| 260 | + end | |
| 261 | + | |
| 262 | + def filter_key?(key) | |
| 263 | + params_filters.any? do |filter| | |
| 264 | + key.to_s.include?(filter) | |
| 265 | + end | |
| 266 | + end | |
| 267 | + | |
| 268 | + def find_session_data | |
| 269 | + self.session_data = args[:session_data] || args[:session] || {} | |
| 270 | + self.session_data = session_data[:data] if session_data[:data] | |
| 271 | + end | |
| 272 | + | |
| 273 | + # Converts the mixed class instances and class names into just names | |
| 274 | + # TODO: move this into Configuration or another class | |
| 275 | + def ignored_class_names | |
| 276 | + ignore.collect do |string_or_class| | |
| 277 | + if string_or_class.respond_to?(:name) | |
| 278 | + string_or_class.name | |
| 279 | + else | |
| 280 | + string_or_class | |
| 281 | + end | |
| 282 | + end | |
| 283 | + end | |
| 284 | + | |
| 285 | + def xml_vars_for(builder, hash) | |
| 286 | + hash.each do |key, value| | |
| 287 | + if value.respond_to?(:to_hash) | |
| 288 | + builder.var(:key => key){|b| xml_vars_for(b, value.to_hash) } | |
| 289 | + else | |
| 290 | + builder.var(value.to_s, :key => key) | |
| 291 | + end | |
| 292 | + end | |
| 293 | + end | |
| 294 | + end | |
| 295 | +end | ... | ... |
vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/sender.rb
0 → 100644
| ... | ... | @@ -0,0 +1,63 @@ |
| 1 | +module HoptoadNotifier | |
| 2 | + # Sends out the notice to Hoptoad | |
| 3 | + class Sender | |
| 4 | + | |
| 5 | + NOTICES_URI = '/notifier_api/v2/notices/'.freeze | |
| 6 | + | |
| 7 | + def initialize(options = {}) | |
| 8 | + [:proxy_host, :proxy_port, :proxy_user, :proxy_pass, :protocol, | |
| 9 | + :host, :port, :secure, :http_open_timeout, :http_read_timeout].each do |option| | |
| 10 | + instance_variable_set("@#{option}", options[option]) | |
| 11 | + end | |
| 12 | + end | |
| 13 | + | |
| 14 | + # Sends the notice data off to Hoptoad for processing. | |
| 15 | + # | |
| 16 | + # @param [String] data The XML notice to be sent off | |
| 17 | + def send_to_hoptoad(data) | |
| 18 | + logger.debug { "Sending request to #{url.to_s}:\n#{data}" } | |
| 19 | + | |
| 20 | + http = | |
| 21 | + Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass). | |
| 22 | + new(url.host, url.port) | |
| 23 | + | |
| 24 | + http.read_timeout = http_read_timeout | |
| 25 | + http.open_timeout = http_open_timeout | |
| 26 | + http.use_ssl = secure | |
| 27 | + | |
| 28 | + response = begin | |
| 29 | + http.post(url.path, data, HEADERS) | |
| 30 | + rescue TimeoutError => e | |
| 31 | + log :error, "Timeout while contacting the Hoptoad server." | |
| 32 | + nil | |
| 33 | + end | |
| 34 | + | |
| 35 | + case response | |
| 36 | + when Net::HTTPSuccess then | |
| 37 | + log :info, "Success: #{response.class}", response | |
| 38 | + else | |
| 39 | + log :error, "Failure: #{response.class}", response | |
| 40 | + end | |
| 41 | + end | |
| 42 | + | |
| 43 | + private | |
| 44 | + | |
| 45 | + attr_reader :proxy_host, :proxy_port, :proxy_user, :proxy_pass, :protocol, | |
| 46 | + :host, :port, :secure, :http_open_timeout, :http_read_timeout | |
| 47 | + | |
| 48 | + def url | |
| 49 | + URI.parse("#{protocol}://#{host}:#{port}").merge(NOTICES_URI) | |
| 50 | + end | |
| 51 | + | |
| 52 | + def log(level, message, response = nil) | |
| 53 | + logger.send level, LOG_PREFIX + message if logger | |
| 54 | + HoptoadNotifier.report_environment_info | |
| 55 | + HoptoadNotifier.report_response_body(response.body) if response && response.respond_to?(:body) | |
| 56 | + end | |
| 57 | + | |
| 58 | + def logger | |
| 59 | + HoptoadNotifier.logger | |
| 60 | + end | |
| 61 | + | |
| 62 | + end | |
| 63 | +end | ... | ... |
vendor/plugins/hoptoad_notifier/lib/hoptoad_tasks.rb
| ... | ... | @@ -2,9 +2,19 @@ require 'net/http' |
| 2 | 2 | require 'uri' |
| 3 | 3 | require 'active_support' |
| 4 | 4 | |
| 5 | +# Capistrano tasks for notifying Hoptoad of deploys | |
| 5 | 6 | module HoptoadTasks |
| 7 | + | |
| 8 | + # Alerts Hoptoad of a deploy. | |
| 9 | + # | |
| 10 | + # @param [Hash] opts Data about the deploy that is set to Hoptoad | |
| 11 | + # | |
| 12 | + # @option opts [String] :rails_env Environment of the deploy (production, staging) | |
| 13 | + # @option opts [String] :scm_revision The given revision/sha that is being deployed | |
| 14 | + # @option opts [String] :scm_repository Address of your repository to help with code lookups | |
| 15 | + # @option opts [String] :local_username Who is deploying | |
| 6 | 16 | def self.deploy(opts = {}) |
| 7 | - if HoptoadNotifier.api_key.blank? | |
| 17 | + if HoptoadNotifier.configuration.api_key.blank? | |
| 8 | 18 | puts "I don't seem to be configured with an API key. Please check your configuration." |
| 9 | 19 | return false |
| 10 | 20 | end |
| ... | ... | @@ -14,10 +24,11 @@ module HoptoadTasks |
| 14 | 24 | return false |
| 15 | 25 | end |
| 16 | 26 | |
| 17 | - params = {:api_key => HoptoadNotifier.api_key} | |
| 27 | + params = {'api_key' => opts.delete(:api_key) || | |
| 28 | + HoptoadNotifier.configuration.api_key} | |
| 18 | 29 | opts.each {|k,v| params["deploy[#{k}]"] = v } |
| 19 | 30 | |
| 20 | - url = URI.parse("http://#{HoptoadNotifier.host}/deploys.txt") | |
| 31 | + url = URI.parse("http://#{HoptoadNotifier.configuration.host || 'hoptoadapp.com'}/deploys.txt") | |
| 21 | 32 | response = Net::HTTP.post_form(url, params) |
| 22 | 33 | puts response.body |
| 23 | 34 | return Net::HTTPSuccess === response | ... | ... |
vendor/plugins/hoptoad_notifier/lib/templates/rescue.erb
0 → 100644
| ... | ... | @@ -0,0 +1,91 @@ |
| 1 | +<script type="text/javascript"> | |
| 2 | +var Hoptoad = { | |
| 3 | + host : <%= host.to_json %>, | |
| 4 | + api_key : <%= api_key.to_json %>, | |
| 5 | + notice : <%= notice.to_json %>, | |
| 6 | + message : 'This error exists in production!', | |
| 7 | + | |
| 8 | + initialize: function() { | |
| 9 | + if (this.initialized) { | |
| 10 | + return; | |
| 11 | + } else { | |
| 12 | + this.initialized = true; | |
| 13 | + } | |
| 14 | + | |
| 15 | + var data = []; | |
| 16 | + | |
| 17 | + for (var key in this.notice) { | |
| 18 | + data[data.length] = 'notice[' + key + ']=' + this.notice[key]; | |
| 19 | + } | |
| 20 | + | |
| 21 | + data[data.length] = 'notice[api_key]=' + this.api_key; | |
| 22 | + data[data.length] = 'callback=Hoptoad.onSuccess'; | |
| 23 | + data[data.length] = '_=' + (new Date()).getTime(); | |
| 24 | + | |
| 25 | + var head = document.getElementsByTagName('head')[0]; | |
| 26 | + var done = false; | |
| 27 | + | |
| 28 | + var | |
| 29 | + script = document.createElement('script'); | |
| 30 | + script.src = 'http://' + this.host + '/notices_api/v1/notices/exist?' + | |
| 31 | + data.join('&'); | |
| 32 | + script.type = 'text/javascript'; | |
| 33 | + script.onload = script.onreadystatechange = function(){ | |
| 34 | + if (!done && (!this.readyState || | |
| 35 | + this.readyState == 'loaded' || this.readyState == 'complete')) { | |
| 36 | + | |
| 37 | + done = true; | |
| 38 | + | |
| 39 | + // Handle memory leak in IE. (via jQuery) | |
| 40 | + script.onload = script.onreadystatechange = null; | |
| 41 | + head.removeChild(script); | |
| 42 | + } | |
| 43 | + }; | |
| 44 | + | |
| 45 | + head.appendChild(script); | |
| 46 | + }, | |
| 47 | + | |
| 48 | + onSuccess: function(error) { | |
| 49 | + var body = document.getElementsByTagName('body')[0]; | |
| 50 | + var text = document.createTextNode(this.message); | |
| 51 | + var element = document.createElement('a'); | |
| 52 | + | |
| 53 | + element.id = 'hoptoad'; | |
| 54 | + element.href = 'http://' + error.subdomain + '.' + this.host + | |
| 55 | + '/projects/' + error.project_id + '/errors/' + error.id; | |
| 56 | + element.appendChild(text); | |
| 57 | + | |
| 58 | + body.insertBefore(element, body.firstChild); | |
| 59 | + | |
| 60 | + var h1 = document.getElementsByTagName('h1')[0]; | |
| 61 | + var pre = document.getElementsByTagName('pre')[0]; | |
| 62 | + var wrapper = document.createElement('div'); | |
| 63 | + | |
| 64 | + wrapper.id = 'wrapper'; | |
| 65 | + wrapper.appendChild(h1); | |
| 66 | + wrapper.appendChild(pre); | |
| 67 | + | |
| 68 | + body.insertBefore(wrapper, body.children[1]); | |
| 69 | + } | |
| 70 | +}; | |
| 71 | + | |
| 72 | +window.onload = function() { | |
| 73 | + Hoptoad.initialize.apply(Hoptoad); | |
| 74 | +}; | |
| 75 | +</script> | |
| 76 | + | |
| 77 | +<style type="text/css"> | |
| 78 | +#hoptoad { | |
| 79 | + background: #FFF url(http://hoptoadapp.com/images/fell-off-the-toad.gif) no-repeat top right; | |
| 80 | + color: #F00; | |
| 81 | + padding: 45px 101px 45px 12px; | |
| 82 | + font-size: 14px; | |
| 83 | + font-weight: bold; | |
| 84 | + display: block; | |
| 85 | + float: right; | |
| 86 | +} | |
| 87 | + | |
| 88 | +#wrapper { | |
| 89 | + padding-right: 360px; | |
| 90 | +} | |
| 91 | +</style> | ... | ... |
| ... | ... | @@ -0,0 +1,8 @@ |
| 1 | +if defined?(ActionController::Base) && !ActionController::Base.include?(HoptoadNotifier::Catcher) | |
| 2 | + ActionController::Base.send(:include, HoptoadNotifier::Catcher) | |
| 3 | +end | |
| 4 | + | |
| 5 | +HoptoadNotifier.configure(true) do |config| | |
| 6 | + config.environment_name = RAILS_ENV | |
| 7 | + config.project_root = RAILS_ROOT | |
| 8 | +end | ... | ... |
vendor/plugins/hoptoad_notifier/recipes/hoptoad.rb
| ... | ... | @@ -6,14 +6,17 @@ |
| 6 | 6 | # |
| 7 | 7 | # Defines deploy:notify_hoptoad which will send information about the deploy to Hoptoad. |
| 8 | 8 | # |
| 9 | -after "deploy:cleanup", "deploy:notify_hoptoad" | |
| 9 | +after "deploy", "deploy:notify_hoptoad" | |
| 10 | +after "deploy:migrations", "deploy:notify_hoptoad" | |
| 10 | 11 | |
| 11 | 12 | namespace :deploy do |
| 12 | 13 | desc "Notify Hoptoad of the deployment" |
| 13 | - task :notify_hoptoad do | |
| 14 | - rails_env = fetch(:rails_env, "production") | |
| 14 | + task :notify_hoptoad, :except => { :no_release => true } do | |
| 15 | + rails_env = fetch(:hoptoad_env, fetch(:rails_env, "production")) | |
| 15 | 16 | local_user = ENV['USER'] || ENV['USERNAME'] |
| 16 | - notify_command = "rake RAILS_ENV=#{rails_env} hoptoad:deploy TO=#{rails_env} REVISION=#{current_revision} REPO=#{repository} USER=#{local_user}" | |
| 17 | + executable = RUBY_PLATFORM.downcase.include?('mswin') ? 'rake.bat' : 'rake' | |
| 18 | + notify_command = "#{executable} hoptoad:deploy TO=#{rails_env} REVISION=#{current_revision} REPO=#{repository} USER=#{local_user}" | |
| 19 | + notify_command << " API_KEY=#{ENV['API_KEY']}" if ENV['API_KEY'] | |
| 17 | 20 | puts "Notifying Hoptoad of Deploy (#{notify_command})" |
| 18 | 21 | `#{notify_command}` |
| 19 | 22 | puts "Hoptoad Notification Complete." | ... | ... |
vendor/plugins/hoptoad_notifier/script/integration_test.rb
| 1 | 1 | #!/usr/bin/env ruby |
| 2 | 2 | |
| 3 | -require File.join(File.dirname(__FILE__), "..", "lib", "hoptoad_notifier") | |
| 4 | - | |
| 5 | -fail "Please supply an API Key as the first argument" if ARGV.empty? | |
| 3 | +require 'logger' | |
| 4 | +require 'fileutils' | |
| 6 | 5 | |
| 7 | 6 | RAILS_ENV = "production" |
| 8 | -RAILS_ROOT = "./" | |
| 7 | +RAILS_ROOT = FileUtils.pwd | |
| 8 | +RAILS_DEFAULT_LOGGER = Logger.new(STDOUT) | |
| 9 | + | |
| 10 | +$: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) | |
| 11 | +require 'hoptoad_notifier' | |
| 12 | +require 'rails/init' | |
| 13 | + | |
| 14 | +fail "Please supply an API Key as the first argument" if ARGV.empty? | |
| 9 | 15 | |
| 10 | 16 | host = ARGV[1] |
| 11 | 17 | host ||= "hoptoadapp.com" |
| ... | ... | @@ -23,6 +29,10 @@ HoptoadNotifier.configure do |config| |
| 23 | 29 | config.host = host |
| 24 | 30 | config.api_key = ARGV.first |
| 25 | 31 | end |
| 26 | -puts "Sending #{secure or "in"}secure notification to project with key #{ARGV.first}" | |
| 32 | +puts "Configuration:" | |
| 33 | +HoptoadNotifier.configuration.to_hash.each do |key, value| | |
| 34 | + puts sprintf("%25s: %s", key.to_s, value.inspect.slice(0, 55)) | |
| 35 | +end | |
| 36 | +puts "Sending #{secure ? "" : "in"}secure notification to project with key #{ARGV.first}" | |
| 27 | 37 | HoptoadNotifier.notify(exception) |
| 28 | 38 | ... | ... |
vendor/plugins/hoptoad_notifier/tasks/hoptoad_notifier_tasks.rake
| ... | ... | @@ -5,24 +5,34 @@ namespace :hoptoad do |
| 5 | 5 | HoptoadTasks.deploy(:rails_env => ENV['TO'], |
| 6 | 6 | :scm_revision => ENV['REVISION'], |
| 7 | 7 | :scm_repository => ENV['REPO'], |
| 8 | - :local_username => ENV['USER']) | |
| 8 | + :local_username => ENV['USER'], | |
| 9 | + :api_key => ENV['API_KEY']) | |
| 10 | + end | |
| 11 | + | |
| 12 | + task :log_stdout do | |
| 13 | + require 'logger' | |
| 14 | + RAILS_DEFAULT_LOGGER = Logger.new(STDOUT) | |
| 9 | 15 | end |
| 10 | 16 | |
| 11 | 17 | desc "Verify your plugin installation by sending a test exception to the hoptoad service" |
| 12 | - task :test => :environment do | |
| 18 | + task :test => ['hoptoad:log_stdout', :environment] do | |
| 19 | + RAILS_DEFAULT_LOGGER.level = Logger::DEBUG | |
| 20 | + | |
| 13 | 21 | require 'action_controller/test_process' |
| 22 | + require 'app/controllers/application' if File.exists?('app/controllers/application.rb') | |
| 14 | 23 | |
| 15 | 24 | request = ActionController::TestRequest.new |
| 16 | - | |
| 17 | 25 | response = ActionController::TestResponse.new |
| 18 | 26 | |
| 19 | 27 | class HoptoadTestingException < RuntimeError; end |
| 20 | 28 | |
| 21 | - unless HoptoadNotifier.api_key | |
| 29 | + unless HoptoadNotifier.configuration.api_key | |
| 22 | 30 | puts "Hoptoad needs an API key configured! Check the README to see how to add it." |
| 23 | 31 | exit |
| 24 | 32 | end |
| 25 | 33 | |
| 34 | + HoptoadNotifier.configuration.development_environments = [] | |
| 35 | + | |
| 26 | 36 | in_controller = ApplicationController.included_modules.include? HoptoadNotifier::Catcher |
| 27 | 37 | in_base = ActionController::Base.included_modules.include? HoptoadNotifier::Catcher |
| 28 | 38 | if !in_controller || !in_base |
| ... | ... | @@ -30,6 +40,11 @@ namespace :hoptoad do |
| 30 | 40 | exit |
| 31 | 41 | end |
| 32 | 42 | |
| 43 | + puts "Configuration:" | |
| 44 | + HoptoadNotifier.configuration.to_hash.each do |key, value| | |
| 45 | + puts sprintf("%25s: %s", key.to_s, value.inspect.slice(0, 55)) | |
| 46 | + end | |
| 47 | + | |
| 33 | 48 | puts 'Setting up the Controller.' |
| 34 | 49 | class ApplicationController |
| 35 | 50 | # This is to bypass any filters that may prevent access to the action. |
| ... | ... | @@ -43,19 +58,27 @@ namespace :hoptoad do |
| 43 | 58 | rescue_action_in_public exception |
| 44 | 59 | end |
| 45 | 60 | |
| 46 | - def public_environment? | |
| 47 | - true | |
| 48 | - end | |
| 49 | - | |
| 50 | 61 | # Ensure we actually have an action to go to. |
| 51 | 62 | def verify; end |
| 52 | 63 | |
| 64 | + def consider_all_requests_local | |
| 65 | + false | |
| 66 | + end | |
| 67 | + | |
| 68 | + def local_request? | |
| 69 | + false | |
| 70 | + end | |
| 71 | + | |
| 53 | 72 | def exception_class |
| 54 | 73 | exception_name = ENV['EXCEPTION'] || "HoptoadTestingException" |
| 55 | 74 | Object.const_get(exception_name) |
| 56 | 75 | rescue |
| 57 | 76 | Object.const_set(exception_name, Class.new(Exception)) |
| 58 | 77 | end |
| 78 | + | |
| 79 | + def logger | |
| 80 | + nil | |
| 81 | + end | |
| 59 | 82 | end |
| 60 | 83 | |
| 61 | 84 | puts 'Processing request.' | ... | ... |
| ... | ... | @@ -0,0 +1,94 @@ |
| 1 | +require File.dirname(__FILE__) + '/helper' | |
| 2 | + | |
| 3 | +class BacktraceTest < Test::Unit::TestCase | |
| 4 | + | |
| 5 | + should "parse a backtrace into lines" do | |
| 6 | + array = [ | |
| 7 | + "app/models/user.rb:13:in `magic'", | |
| 8 | + "app/controllers/users_controller.rb:8:in `index'" | |
| 9 | + ] | |
| 10 | + | |
| 11 | + backtrace = HoptoadNotifier::Backtrace.parse(array) | |
| 12 | + | |
| 13 | + line = backtrace.lines.first | |
| 14 | + assert_equal '13', line.number | |
| 15 | + assert_equal 'app/models/user.rb', line.file | |
| 16 | + assert_equal 'magic', line.method | |
| 17 | + | |
| 18 | + line = backtrace.lines.last | |
| 19 | + assert_equal '8', line.number | |
| 20 | + assert_equal 'app/controllers/users_controller.rb', line.file | |
| 21 | + assert_equal 'index', line.method | |
| 22 | + end | |
| 23 | + | |
| 24 | + should "be equal with equal lines" do | |
| 25 | + one = build_backtrace_array | |
| 26 | + two = one.dup | |
| 27 | + assert_equal one, two | |
| 28 | + | |
| 29 | + assert_equal HoptoadNotifier::Backtrace.parse(one), HoptoadNotifier::Backtrace.parse(two) | |
| 30 | + end | |
| 31 | + | |
| 32 | + should "parse massive one-line exceptions into multiple lines" do | |
| 33 | + original_backtrace = HoptoadNotifier::Backtrace. | |
| 34 | + parse(["one:1:in `one'\n two:2:in `two'\n three:3:in `three`"]) | |
| 35 | + expected_backtrace = HoptoadNotifier::Backtrace. | |
| 36 | + parse(["one:1:in `one'", "two:2:in `two'", "three:3:in `three`"]) | |
| 37 | + | |
| 38 | + assert_equal expected_backtrace, original_backtrace | |
| 39 | + end | |
| 40 | + | |
| 41 | + context "with a project root" do | |
| 42 | + setup do | |
| 43 | + @project_root = '/some/path' | |
| 44 | + HoptoadNotifier.configure {|config| config.project_root = @project_root } | |
| 45 | + end | |
| 46 | + | |
| 47 | + teardown do | |
| 48 | + reset_config | |
| 49 | + end | |
| 50 | + | |
| 51 | + should "filter out the project root" do | |
| 52 | + backtrace_with_root = HoptoadNotifier::Backtrace.parse( | |
| 53 | + ["#{@project_root}/app/models/user.rb:7:in `latest'", | |
| 54 | + "#{@project_root}/app/controllers/users_controller.rb:13:in `index'", | |
| 55 | + "/lib/something.rb:41:in `open'"], | |
| 56 | + :filters => default_filters) | |
| 57 | + backtrace_without_root = HoptoadNotifier::Backtrace.parse( | |
| 58 | + ["[PROJECT_ROOT]/app/models/user.rb:7:in `latest'", | |
| 59 | + "[PROJECT_ROOT]/app/controllers/users_controller.rb:13:in `index'", | |
| 60 | + "/lib/something.rb:41:in `open'"]) | |
| 61 | + | |
| 62 | + assert_equal backtrace_without_root, backtrace_with_root | |
| 63 | + end | |
| 64 | + end | |
| 65 | + | |
| 66 | + should "remove notifier trace" do | |
| 67 | + inside_notifier = ['lib/hoptoad_notifier.rb:13:in `voodoo`'] | |
| 68 | + outside_notifier = ['users_controller:8:in `index`'] | |
| 69 | + | |
| 70 | + without_inside = HoptoadNotifier::Backtrace.parse(outside_notifier) | |
| 71 | + with_inside = HoptoadNotifier::Backtrace.parse(inside_notifier + outside_notifier, | |
| 72 | + :filters => default_filters) | |
| 73 | + | |
| 74 | + assert_equal without_inside, with_inside | |
| 75 | + end | |
| 76 | + | |
| 77 | + should "run filters on the backtrace" do | |
| 78 | + filters = [lambda { |line| line.sub('foo', 'bar') }] | |
| 79 | + input = HoptoadNotifier::Backtrace.parse(["foo:13:in `one'", "baz:14:in `two'"], | |
| 80 | + :filters => filters) | |
| 81 | + expected = HoptoadNotifier::Backtrace.parse(["bar:13:in `one'", "baz:14:in `two'"]) | |
| 82 | + assert_equal expected, input | |
| 83 | + end | |
| 84 | + | |
| 85 | + def build_backtrace_array | |
| 86 | + ["app/models/user.rb:13:in `magic'", | |
| 87 | + "app/controllers/users_controller.rb:8:in `index'"] | |
| 88 | + end | |
| 89 | + | |
| 90 | + def default_filters | |
| 91 | + HoptoadNotifier::Configuration::DEFAULT_BACKTRACE_FILTERS | |
| 92 | + end | |
| 93 | + | |
| 94 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,314 @@ |
| 1 | +require File.dirname(__FILE__) + '/helper' | |
| 2 | + | |
| 3 | +class CatcherTest < Test::Unit::TestCase | |
| 4 | + | |
| 5 | + include DefinesConstants | |
| 6 | + | |
| 7 | + def setup | |
| 8 | + super | |
| 9 | + reset_config | |
| 10 | + HoptoadNotifier.sender = CollectingSender.new | |
| 11 | + define_constant('RAILS_ROOT', '/path/to/rails/root') | |
| 12 | + end | |
| 13 | + | |
| 14 | + def ignore(exception_class) | |
| 15 | + HoptoadNotifier.configuration.ignore << exception_class | |
| 16 | + end | |
| 17 | + | |
| 18 | + def build_controller_class(&definition) | |
| 19 | + returning Class.new(ActionController::Base) do |klass| | |
| 20 | + klass.__send__(:include, HoptoadNotifier::Catcher) | |
| 21 | + klass.class_eval(&definition) if definition | |
| 22 | + define_constant('HoptoadTestController', klass) | |
| 23 | + end | |
| 24 | + end | |
| 25 | + | |
| 26 | + def assert_sent_hash(hash, xpath) | |
| 27 | + hash.each do |key, value| | |
| 28 | + element_xpath = "#{xpath}/var[@key = '#{key}']" | |
| 29 | + if value.respond_to?(:to_hash) | |
| 30 | + assert_sent_hash value.to_hash, element_xpath | |
| 31 | + else | |
| 32 | + assert_sent_element value.to_s, element_xpath | |
| 33 | + end | |
| 34 | + end | |
| 35 | + end | |
| 36 | + | |
| 37 | + def assert_sent_element(value, xpath) | |
| 38 | + assert_valid_node last_sent_notice_document, xpath, value | |
| 39 | + end | |
| 40 | + | |
| 41 | + def assert_sent_request_info_for(request) | |
| 42 | + params = request.parameters.to_hash | |
| 43 | + assert_sent_hash params, '/notice/request/params' | |
| 44 | + assert_sent_element params['controller'], '/notice/request/component' | |
| 45 | + assert_sent_element params['action'], '/notice/request/action' | |
| 46 | + assert_sent_element url_from_request(request), '/notice/request/url' | |
| 47 | + assert_sent_hash request.env, '/notice/request/cgi-data' | |
| 48 | + end | |
| 49 | + | |
| 50 | + def url_from_request(request) | |
| 51 | + url = "#{request.protocol}#{request.host}" | |
| 52 | + | |
| 53 | + unless [80, 443].include?(request.port) | |
| 54 | + url << ":#{request.port}" | |
| 55 | + end | |
| 56 | + | |
| 57 | + url << request.request_uri | |
| 58 | + url | |
| 59 | + end | |
| 60 | + | |
| 61 | + def sender | |
| 62 | + HoptoadNotifier.sender | |
| 63 | + end | |
| 64 | + | |
| 65 | + def last_sent_notice_xml | |
| 66 | + sender.collected.last | |
| 67 | + end | |
| 68 | + | |
| 69 | + def last_sent_notice_document | |
| 70 | + assert_not_nil xml = last_sent_notice_xml, "No xml was sent" | |
| 71 | + Nokogiri::XML.parse(xml) | |
| 72 | + end | |
| 73 | + | |
| 74 | + def process_action(opts = {}, &action) | |
| 75 | + opts[:request] ||= ActionController::TestRequest.new | |
| 76 | + opts[:response] ||= ActionController::TestResponse.new | |
| 77 | + klass = build_controller_class do | |
| 78 | + cattr_accessor :local | |
| 79 | + define_method(:index, &action) | |
| 80 | + def local_request? | |
| 81 | + local | |
| 82 | + end | |
| 83 | + end | |
| 84 | + if opts[:filters] | |
| 85 | + klass.filter_parameter_logging *opts[:filters] | |
| 86 | + end | |
| 87 | + if opts[:user_agent] | |
| 88 | + if opts[:request].respond_to?(:user_agent=) | |
| 89 | + opts[:request].user_agent = opts[:user_agent] | |
| 90 | + else | |
| 91 | + opts[:request].env["HTTP_USER_AGENT"] = opts[:user_agent] | |
| 92 | + end | |
| 93 | + end | |
| 94 | + if opts[:port] | |
| 95 | + opts[:request].port = opts[:port] | |
| 96 | + end | |
| 97 | + klass.consider_all_requests_local = opts[:all_local] | |
| 98 | + klass.local = opts[:local] | |
| 99 | + controller = klass.new | |
| 100 | + controller.stubs(:rescue_action_in_public_without_hoptoad) | |
| 101 | + opts[:request].query_parameters = opts[:request].query_parameters.merge(opts[:params] || {}) | |
| 102 | + opts[:request].session = ActionController::TestSession.new(opts[:session] || {}) | |
| 103 | + controller.process(opts[:request], opts[:response]) | |
| 104 | + controller | |
| 105 | + end | |
| 106 | + | |
| 107 | + def process_action_with_manual_notification(args = {}) | |
| 108 | + process_action(args) do | |
| 109 | + notify_hoptoad(:error_message => 'fail') | |
| 110 | + # Rails will raise a template error if we don't render something | |
| 111 | + render :nothing => true | |
| 112 | + end | |
| 113 | + end | |
| 114 | + | |
| 115 | + def process_action_with_automatic_notification(args = {}) | |
| 116 | + process_action(args) { raise "Hello" } | |
| 117 | + end | |
| 118 | + | |
| 119 | + should "deliver notices from exceptions raised in public requests" do | |
| 120 | + process_action_with_automatic_notification | |
| 121 | + assert_caught_and_sent | |
| 122 | + end | |
| 123 | + | |
| 124 | + should "not deliver notices from exceptions in local requests" do | |
| 125 | + process_action_with_automatic_notification(:local => true) | |
| 126 | + assert_caught_and_not_sent | |
| 127 | + end | |
| 128 | + | |
| 129 | + should "not deliver notices from exceptions when all requests are local" do | |
| 130 | + process_action_with_automatic_notification(:all_local => true) | |
| 131 | + assert_caught_and_not_sent | |
| 132 | + end | |
| 133 | + | |
| 134 | + should "not deliver notices from actions that don't raise" do | |
| 135 | + controller = process_action { render :text => 'Hello' } | |
| 136 | + assert_caught_and_not_sent | |
| 137 | + assert_equal 'Hello', controller.response.body | |
| 138 | + end | |
| 139 | + | |
| 140 | + should "not deliver ignored exceptions raised by actions" do | |
| 141 | + ignore(RuntimeError) | |
| 142 | + process_action_with_automatic_notification | |
| 143 | + assert_caught_and_not_sent | |
| 144 | + end | |
| 145 | + | |
| 146 | + should "deliver ignored exception raised manually" do | |
| 147 | + ignore(RuntimeError) | |
| 148 | + process_action_with_manual_notification | |
| 149 | + assert_caught_and_sent | |
| 150 | + end | |
| 151 | + | |
| 152 | + should "deliver manually sent notices in public requests" do | |
| 153 | + process_action_with_manual_notification | |
| 154 | + assert_caught_and_sent | |
| 155 | + end | |
| 156 | + | |
| 157 | + should "not deliver manually sent notices in local requests" do | |
| 158 | + process_action_with_manual_notification(:local => true) | |
| 159 | + assert_caught_and_not_sent | |
| 160 | + end | |
| 161 | + | |
| 162 | + should "not deliver manually sent notices when all requests are local" do | |
| 163 | + process_action_with_manual_notification(:all_local => true) | |
| 164 | + assert_caught_and_not_sent | |
| 165 | + end | |
| 166 | + | |
| 167 | + should "continue with default behavior after delivering an exception" do | |
| 168 | + controller = process_action_with_automatic_notification(:public => true) | |
| 169 | + # TODO: can we test this without stubbing? | |
| 170 | + assert_received(controller, :rescue_action_in_public_without_hoptoad) | |
| 171 | + end | |
| 172 | + | |
| 173 | + should "not create actions from Hoptoad methods" do | |
| 174 | + controller = build_controller_class.new | |
| 175 | + assert_equal [], HoptoadNotifier::Catcher.instance_methods | |
| 176 | + end | |
| 177 | + | |
| 178 | + should "ignore exceptions when user agent is being ignored by regular expression" do | |
| 179 | + HoptoadNotifier.configuration.ignore_user_agent_only = [/Ignored/] | |
| 180 | + process_action_with_automatic_notification(:user_agent => 'ShouldBeIgnored') | |
| 181 | + assert_caught_and_not_sent | |
| 182 | + end | |
| 183 | + | |
| 184 | + should "ignore exceptions when user agent is being ignored by string" do | |
| 185 | + HoptoadNotifier.configuration.ignore_user_agent_only = ['IgnoredUserAgent'] | |
| 186 | + process_action_with_automatic_notification(:user_agent => 'IgnoredUserAgent') | |
| 187 | + assert_caught_and_not_sent | |
| 188 | + end | |
| 189 | + | |
| 190 | + should "not ignore exceptions when user agent is not being ignored" do | |
| 191 | + HoptoadNotifier.configuration.ignore_user_agent_only = ['IgnoredUserAgent'] | |
| 192 | + process_action_with_automatic_notification(:user_agent => 'NonIgnoredAgent') | |
| 193 | + assert_caught_and_sent | |
| 194 | + end | |
| 195 | + | |
| 196 | + should "send session data for manual notifications" do | |
| 197 | + data = { 'one' => 'two' } | |
| 198 | + process_action_with_manual_notification(:session => data) | |
| 199 | + assert_sent_hash data, "/notice/request/session" | |
| 200 | + end | |
| 201 | + | |
| 202 | + should "send session data for automatic notification" do | |
| 203 | + data = { 'one' => 'two' } | |
| 204 | + process_action_with_automatic_notification(:session => data) | |
| 205 | + assert_sent_hash data, "/notice/request/session" | |
| 206 | + end | |
| 207 | + | |
| 208 | + should "send request data for manual notification" do | |
| 209 | + params = { 'controller' => "hoptoad_test", 'action' => "index" } | |
| 210 | + controller = process_action_with_manual_notification(:params => params) | |
| 211 | + assert_sent_request_info_for controller.request | |
| 212 | + end | |
| 213 | + | |
| 214 | + should "send request data for manual notification with non-standard port" do | |
| 215 | + params = { 'controller' => "hoptoad_test", 'action' => "index" } | |
| 216 | + controller = process_action_with_manual_notification(:params => params, :port => 81) | |
| 217 | + assert_sent_request_info_for controller.request | |
| 218 | + end | |
| 219 | + | |
| 220 | + should "send request data for automatic notification" do | |
| 221 | + params = { 'controller' => "hoptoad_test", 'action' => "index" } | |
| 222 | + controller = process_action_with_automatic_notification(:params => params) | |
| 223 | + assert_sent_request_info_for controller.request | |
| 224 | + end | |
| 225 | + | |
| 226 | + should "send request data for automatic notification with non-standard port" do | |
| 227 | + params = { 'controller' => "hoptoad_test", 'action' => "index" } | |
| 228 | + controller = process_action_with_automatic_notification(:params => params, :port => 81) | |
| 229 | + assert_sent_request_info_for controller.request | |
| 230 | + end | |
| 231 | + | |
| 232 | + should "use standard rails logging filters on params and env" do | |
| 233 | + filtered_params = { "abc" => "123", | |
| 234 | + "def" => "456", | |
| 235 | + "ghi" => "[FILTERED]" } | |
| 236 | + ENV['ghi'] = 'abc' | |
| 237 | + filtered_env = { 'ghi' => '[FILTERED]' } | |
| 238 | + filtered_cgi = { 'REQUEST_METHOD' => '[FILTERED]' } | |
| 239 | + | |
| 240 | + process_action_with_automatic_notification(:filters => [:ghi, :request_method], | |
| 241 | + :params => { "abc" => "123", | |
| 242 | + "def" => "456", | |
| 243 | + "ghi" => "789" }) | |
| 244 | + assert_sent_hash filtered_params, '/notice/request/params' | |
| 245 | + assert_sent_hash filtered_cgi, '/notice/request/cgi-data' | |
| 246 | + end | |
| 247 | + | |
| 248 | + context "for a local error with development lookup enabled" do | |
| 249 | + setup do | |
| 250 | + HoptoadNotifier.configuration.development_lookup = true | |
| 251 | + HoptoadNotifier.stubs(:build_lookup_hash_for).returns({ :awesome => 2 }) | |
| 252 | + | |
| 253 | + @controller = process_action_with_automatic_notification(:local => true) | |
| 254 | + @response = @controller.response | |
| 255 | + end | |
| 256 | + | |
| 257 | + should "append custom CSS and JS to response body for a local error" do | |
| 258 | + assert_match /text\/css/, @response.body | |
| 259 | + assert_match /text\/javascript/, @response.body | |
| 260 | + end | |
| 261 | + | |
| 262 | + should "contain host, API key and notice JSON" do | |
| 263 | + assert_match HoptoadNotifier.configuration.host.to_json, @response.body | |
| 264 | + assert_match HoptoadNotifier.configuration.api_key.to_json, @response.body | |
| 265 | + assert_match ({ :awesome => 2 }).to_json, @response.body | |
| 266 | + end | |
| 267 | + end | |
| 268 | + | |
| 269 | + context "for a local error with development lookup disabled" do | |
| 270 | + setup do | |
| 271 | + HoptoadNotifier.configuration.development_lookup = false | |
| 272 | + | |
| 273 | + @controller = process_action_with_automatic_notification(:local => true) | |
| 274 | + @response = @controller.response | |
| 275 | + end | |
| 276 | + | |
| 277 | + should "not append custom CSS and JS to response for a local error" do | |
| 278 | + assert_no_match /text\/css/, @response.body | |
| 279 | + assert_no_match /text\/javascript/, @response.body | |
| 280 | + end | |
| 281 | + end | |
| 282 | + | |
| 283 | + should "call session.to_hash if available" do | |
| 284 | + hash_data = {:key => :value} | |
| 285 | + | |
| 286 | + session = ActionController::TestSession.new | |
| 287 | + ActionController::TestSession.stubs(:new).returns(session) | |
| 288 | + session.stubs(:to_hash).returns(hash_data) | |
| 289 | + | |
| 290 | + process_action_with_automatic_notification | |
| 291 | + assert_received(session, :to_hash) | |
| 292 | + assert_received(session, :data) { |expect| expect.never } | |
| 293 | + assert_caught_and_sent | |
| 294 | + end | |
| 295 | + | |
| 296 | + should "call session.data if session.to_hash is undefined" do | |
| 297 | + hash_data = {:key => :value} | |
| 298 | + | |
| 299 | + session = ActionController::TestSession.new | |
| 300 | + ActionController::TestSession.stubs(:new).returns(session) | |
| 301 | + session.stubs(:data).returns(hash_data) | |
| 302 | + if session.respond_to?(:to_hash) | |
| 303 | + class << session | |
| 304 | + undef to_hash | |
| 305 | + end | |
| 306 | + end | |
| 307 | + | |
| 308 | + process_action_with_automatic_notification | |
| 309 | + assert_received(session, :to_hash) { |expect| expect.never } | |
| 310 | + assert_received(session, :data) { |expect| expect.at_least_once } | |
| 311 | + assert_caught_and_sent | |
| 312 | + end | |
| 313 | + | |
| 314 | +end | ... | ... |
vendor/plugins/hoptoad_notifier/test/configuration_test.rb
| 1 | 1 | require File.dirname(__FILE__) + '/helper' |
| 2 | 2 | |
| 3 | -class ConfigurationTest < ActiveSupport::TestCase | |
| 4 | - context "HoptoadNotifier configuration" do | |
| 5 | - setup do | |
| 6 | - @controller = HoptoadController.new | |
| 7 | - class ::HoptoadController | |
| 8 | - include HoptoadNotifier::Catcher | |
| 9 | - def rescue_action e | |
| 10 | - rescue_action_in_public e | |
| 11 | - end | |
| 12 | - end | |
| 13 | - assert @controller.methods.include?("notify_hoptoad") | |
| 14 | - end | |
| 3 | +class ConfigurationTest < Test::Unit::TestCase | |
| 4 | + | |
| 5 | + include DefinesConstants | |
| 6 | + | |
| 7 | + should "provide default values" do | |
| 8 | + assert_config_default :proxy_host, nil | |
| 9 | + assert_config_default :proxy_port, nil | |
| 10 | + assert_config_default :proxy_user, nil | |
| 11 | + assert_config_default :proxy_pass, nil | |
| 12 | + assert_config_default :project_root, nil | |
| 13 | + assert_config_default :environment_name, nil | |
| 14 | + assert_config_default :notifier_version, HoptoadNotifier::VERSION | |
| 15 | + assert_config_default :notifier_name, 'Hoptoad Notifier' | |
| 16 | + assert_config_default :notifier_url, 'http://hoptoadapp.com' | |
| 17 | + assert_config_default :secure, false | |
| 18 | + assert_config_default :host, 'hoptoadapp.com' | |
| 19 | + assert_config_default :http_open_timeout, 2 | |
| 20 | + assert_config_default :http_read_timeout, 5 | |
| 21 | + assert_config_default :ignore_by_filters, [] | |
| 22 | + assert_config_default :ignore_user_agent, [] | |
| 23 | + assert_config_default :params_filters, | |
| 24 | + HoptoadNotifier::Configuration::DEFAULT_PARAMS_FILTERS | |
| 25 | + assert_config_default :backtrace_filters, | |
| 26 | + HoptoadNotifier::Configuration::DEFAULT_BACKTRACE_FILTERS | |
| 27 | + assert_config_default :ignore, | |
| 28 | + HoptoadNotifier::Configuration::IGNORE_DEFAULT | |
| 29 | + assert_config_default :development_lookup, true | |
| 30 | + end | |
| 15 | 31 | |
| 16 | - should "be done with a block" do | |
| 17 | - HoptoadNotifier.configure do |config| | |
| 18 | - config.host = "host" | |
| 19 | - config.port = 3333 | |
| 20 | - config.secure = true | |
| 21 | - config.api_key = "1234567890abcdef" | |
| 22 | - config.ignore << [ RuntimeError ] | |
| 23 | - config.ignore_user_agent << 'UserAgentString' | |
| 24 | - config.ignore_user_agent << /UserAgentRegexp/ | |
| 25 | - config.proxy_host = 'proxyhost1' | |
| 26 | - config.proxy_port = '80' | |
| 27 | - config.proxy_user = 'user' | |
| 28 | - config.proxy_pass = 'secret' | |
| 29 | - config.http_open_timeout = 2 | |
| 30 | - config.http_read_timeout = 5 | |
| 31 | - end | |
| 32 | - | |
| 33 | - assert_equal "host", HoptoadNotifier.host | |
| 34 | - assert_equal 3333, HoptoadNotifier.port | |
| 35 | - assert_equal true, HoptoadNotifier.secure | |
| 36 | - assert_equal "1234567890abcdef", HoptoadNotifier.api_key | |
| 37 | - assert_equal 'proxyhost1', HoptoadNotifier.proxy_host | |
| 38 | - assert_equal '80', HoptoadNotifier.proxy_port | |
| 39 | - assert_equal 'user', HoptoadNotifier.proxy_user | |
| 40 | - assert_equal 'secret', HoptoadNotifier.proxy_pass | |
| 41 | - assert_equal 2, HoptoadNotifier.http_open_timeout | |
| 42 | - assert_equal 5, HoptoadNotifier.http_read_timeout | |
| 43 | - assert_equal HoptoadNotifier::IGNORE_USER_AGENT_DEFAULT + ['UserAgentString', /UserAgentRegexp/], | |
| 44 | - HoptoadNotifier.ignore_user_agent | |
| 45 | - assert_equal HoptoadNotifier::IGNORE_DEFAULT + [RuntimeError], | |
| 46 | - HoptoadNotifier.ignore | |
| 47 | - end | |
| 32 | + should "provide default values for secure connections" do | |
| 33 | + config = HoptoadNotifier::Configuration.new | |
| 34 | + config.secure = true | |
| 35 | + assert_equal 443, config.port | |
| 36 | + assert_equal 'https', config.protocol | |
| 37 | + end | |
| 48 | 38 | |
| 49 | - should "set a default host" do | |
| 50 | - HoptoadNotifier.instance_variable_set("@host",nil) | |
| 51 | - assert_equal "hoptoadapp.com", HoptoadNotifier.host | |
| 52 | - end | |
| 39 | + should "provide default values for insecure connections" do | |
| 40 | + config = HoptoadNotifier::Configuration.new | |
| 41 | + config.secure = false | |
| 42 | + assert_equal 80, config.port | |
| 43 | + assert_equal 'http', config.protocol | |
| 44 | + end | |
| 53 | 45 | |
| 54 | - [File.open(__FILE__), Proc.new { puts "boo!" }, Module.new].each do |object| | |
| 55 | - should "remove #{object.class} when cleaning environment" do | |
| 56 | - HoptoadNotifier.configure {} | |
| 57 | - notice = @controller.send(:normalize_notice, {}) | |
| 58 | - notice[:environment][:strange_object] = object | |
| 46 | + should "not cache inferred ports" do | |
| 47 | + config = HoptoadNotifier::Configuration.new | |
| 48 | + config.secure = false | |
| 49 | + config.port | |
| 50 | + config.secure = true | |
| 51 | + assert_equal 443, config.port | |
| 52 | + end | |
| 59 | 53 | |
| 60 | - assert_nil @controller.send(:clean_non_serializable_data, notice)[:environment][:strange_object] | |
| 61 | - end | |
| 62 | - end | |
| 54 | + should "allow values to be overwritten" do | |
| 55 | + assert_config_overridable :proxy_host | |
| 56 | + assert_config_overridable :proxy_port | |
| 57 | + assert_config_overridable :proxy_user | |
| 58 | + assert_config_overridable :proxy_pass | |
| 59 | + assert_config_overridable :secure | |
| 60 | + assert_config_overridable :host | |
| 61 | + assert_config_overridable :port | |
| 62 | + assert_config_overridable :http_open_timeout | |
| 63 | + assert_config_overridable :http_read_timeout | |
| 64 | + assert_config_overridable :project_root | |
| 65 | + assert_config_overridable :notifier_version | |
| 66 | + assert_config_overridable :notifier_name | |
| 67 | + assert_config_overridable :notifier_url | |
| 68 | + assert_config_overridable :environment_name | |
| 69 | + assert_config_overridable :development_lookup | |
| 70 | + end | |
| 63 | 71 | |
| 64 | - [123, "string", 123_456_789_123_456_789, [:a, :b], {:a => 1}, HashWithIndifferentAccess.new].each do |object| | |
| 65 | - should "not remove #{object.class} when cleaning environment" do | |
| 66 | - HoptoadNotifier.configure {} | |
| 67 | - notice = @controller.send(:normalize_notice, {}) | |
| 68 | - notice[:environment][:strange_object] = object | |
| 72 | + should "have an api key" do | |
| 73 | + assert_config_overridable :api_key | |
| 74 | + end | |
| 69 | 75 | |
| 70 | - assert_equal object, @controller.send(:clean_non_serializable_data, notice)[:environment][:strange_object] | |
| 71 | - end | |
| 76 | + should "act like a hash" do | |
| 77 | + config = HoptoadNotifier::Configuration.new | |
| 78 | + hash = config.to_hash | |
| 79 | + [:api_key, :backtrace_filters, :development_environments, | |
| 80 | + :environment_name, :host, :http_open_timeout, | |
| 81 | + :http_read_timeout, :ignore, :ignore_by_filters, :ignore_user_agent, | |
| 82 | + :notifier_name, :notifier_url, :notifier_version, :params_filters, | |
| 83 | + :project_root, :port, :protocol, :proxy_host, :proxy_pass, :proxy_port, | |
| 84 | + :proxy_user, :secure, :development_lookup].each do |option| | |
| 85 | + assert_equal config[option], hash[option], "Wrong value for #{option}" | |
| 72 | 86 | end |
| 87 | + end | |
| 73 | 88 | |
| 74 | - should "remove notifier trace when cleaning backtrace" do | |
| 75 | - HoptoadNotifier.configure {} | |
| 76 | - notice = @controller.send(:normalize_notice, {}) | |
| 89 | + should "be mergable" do | |
| 90 | + config = HoptoadNotifier::Configuration.new | |
| 91 | + hash = config.to_hash | |
| 92 | + assert_equal hash.merge(:key => 'value'), config.merge(:key => 'value') | |
| 93 | + end | |
| 77 | 94 | |
| 78 | - assert notice[:backtrace].grep(%r{lib/hoptoad_notifier.rb}).any?, notice[:backtrace].inspect | |
| 95 | + should "allow param filters to be appended" do | |
| 96 | + assert_appends_value :params_filters | |
| 97 | + end | |
| 79 | 98 | |
| 80 | - dirty_backtrace = @controller.send(:clean_hoptoad_backtrace, notice[:backtrace]) | |
| 81 | - dirty_backtrace.each do |line| | |
| 82 | - assert_no_match %r{lib/hoptoad_notifier.rb}, line | |
| 83 | - end | |
| 84 | - end | |
| 99 | + should "warn when attempting to read environment filters" do | |
| 100 | + config = HoptoadNotifier::Configuration.new | |
| 101 | + config. | |
| 102 | + expects(:warn). | |
| 103 | + with(regexp_matches(/deprecated/i)) | |
| 104 | + assert_equal [], config.environment_filters | |
| 105 | + end | |
| 85 | 106 | |
| 86 | - should "add filters to the backtrace_filters" do | |
| 87 | - assert_difference "HoptoadNotifier.backtrace_filters.length", 5 do | |
| 88 | - HoptoadNotifier.configure do |config| | |
| 89 | - config.filter_backtrace do |line| | |
| 90 | - line = "1234" | |
| 91 | - end | |
| 92 | - end | |
| 93 | - end | |
| 107 | + should "allow ignored user agents to be appended" do | |
| 108 | + assert_appends_value :ignore_user_agent | |
| 109 | + end | |
| 94 | 110 | |
| 95 | - assert_equal %w( 1234 1234 ), @controller.send(:clean_hoptoad_backtrace, %w( foo bar )) | |
| 111 | + should "allow backtrace filters to be appended" do | |
| 112 | + assert_appends_value(:backtrace_filters) do |config| | |
| 113 | + new_filter = lambda {} | |
| 114 | + config.filter_backtrace(&new_filter) | |
| 115 | + new_filter | |
| 96 | 116 | end |
| 117 | + end | |
| 97 | 118 | |
| 98 | - should "use standard rails logging filters on params and env" do | |
| 99 | - ::HoptoadController.class_eval do | |
| 100 | - filter_parameter_logging :ghi | |
| 101 | - end | |
| 102 | - | |
| 103 | - expected = {"notice" => {"request" => {"params" => {"abc" => "123", "def" => "456", "ghi" => "[FILTERED]"}}, | |
| 104 | - "environment" => {"abc" => "123", "ghi" => "[FILTERED]"}}} | |
| 105 | - notice = {"notice" => {"request" => {"params" => {"abc" => "123", "def" => "456", "ghi" => "789"}}, | |
| 106 | - "environment" => {"abc" => "123", "ghi" => "789"}}} | |
| 107 | - assert @controller.respond_to?(:filter_parameters) | |
| 108 | - assert_equal( expected[:notice], @controller.send(:clean_notice, notice)[:notice] ) | |
| 119 | + should "allow ignore by filters to be appended" do | |
| 120 | + assert_appends_value(:ignore_by_filters) do |config| | |
| 121 | + new_filter = lambda {} | |
| 122 | + config.ignore_by_filter(&new_filter) | |
| 123 | + new_filter | |
| 109 | 124 | end |
| 125 | + end | |
| 110 | 126 | |
| 111 | - should "add filters to the params filters" do | |
| 112 | - assert_difference "HoptoadNotifier.params_filters.length", 2 do | |
| 113 | - HoptoadNotifier.configure do |config| | |
| 114 | - config.params_filters << "abc" | |
| 115 | - config.params_filters << "def" | |
| 116 | - end | |
| 117 | - end | |
| 127 | + should "allow ignored exceptions to be appended" do | |
| 128 | + config = HoptoadNotifier::Configuration.new | |
| 129 | + original_filters = config.ignore.dup | |
| 130 | + new_filter = 'hello' | |
| 131 | + config.ignore << new_filter | |
| 132 | + assert_same_elements original_filters + [new_filter], config.ignore | |
| 133 | + end | |
| 118 | 134 | |
| 119 | - assert HoptoadNotifier.params_filters.include?( "abc" ) | |
| 120 | - assert HoptoadNotifier.params_filters.include?( "def" ) | |
| 135 | + should "allow ignored exceptions to be replaced" do | |
| 136 | + assert_replaces(:ignore, :ignore_only=) | |
| 137 | + end | |
| 121 | 138 | |
| 122 | - assert_equal( {:abc => "[FILTERED]", :def => "[FILTERED]", :ghi => "789"}, | |
| 123 | - @controller.send(:clean_hoptoad_params, :abc => "123", :def => "456", :ghi => "789" ) ) | |
| 124 | - end | |
| 139 | + should "allow ignored user agents to be replaced" do | |
| 140 | + assert_replaces(:ignore_user_agent, :ignore_user_agent_only=) | |
| 141 | + end | |
| 125 | 142 | |
| 126 | - should "add filters to the environment filters" do | |
| 127 | - assert_difference "HoptoadNotifier.environment_filters.length", 2 do | |
| 128 | - HoptoadNotifier.configure do |config| | |
| 129 | - config.environment_filters << "secret" | |
| 130 | - config.environment_filters << "supersecret" | |
| 131 | - end | |
| 132 | - end | |
| 143 | + should "use development and test as development environments by default" do | |
| 144 | + config = HoptoadNotifier::Configuration.new | |
| 145 | + assert_same_elements %w(development test cucumber), config.development_environments | |
| 146 | + end | |
| 133 | 147 | |
| 134 | - assert HoptoadNotifier.environment_filters.include?( "secret" ) | |
| 135 | - assert HoptoadNotifier.environment_filters.include?( "supersecret" ) | |
| 148 | + should "be public in a public environment" do | |
| 149 | + config = HoptoadNotifier::Configuration.new | |
| 150 | + config.development_environments = %w(development) | |
| 151 | + config.environment_name = 'production' | |
| 152 | + assert config.public? | |
| 153 | + end | |
| 136 | 154 | |
| 137 | - assert_equal( {:secret => "[FILTERED]", :supersecret => "[FILTERED]", :ghi => "789"}, | |
| 138 | - @controller.send(:clean_hoptoad_environment, :secret => "123", :supersecret => "456", :ghi => "789" ) ) | |
| 139 | - end | |
| 155 | + should "not be public in a development environment" do | |
| 156 | + config = HoptoadNotifier::Configuration.new | |
| 157 | + config.development_environments = %w(staging) | |
| 158 | + config.environment_name = 'staging' | |
| 159 | + assert !config.public? | |
| 160 | + end | |
| 140 | 161 | |
| 141 | - should "have at default ignored exceptions" do | |
| 142 | - assert HoptoadNotifier::IGNORE_DEFAULT.any? | |
| 162 | + should "be public without an environment name" do | |
| 163 | + config = HoptoadNotifier::Configuration.new | |
| 164 | + assert config.public? | |
| 165 | + end | |
| 166 | + | |
| 167 | + def assert_config_default(option, default_value, config = nil) | |
| 168 | + config ||= HoptoadNotifier::Configuration.new | |
| 169 | + assert_equal default_value, config.send(option) | |
| 170 | + end | |
| 171 | + | |
| 172 | + def assert_config_overridable(option, value = 'a value') | |
| 173 | + config = HoptoadNotifier::Configuration.new | |
| 174 | + config.send(:"#{option}=", value) | |
| 175 | + assert_equal value, config.send(option) | |
| 176 | + end | |
| 177 | + | |
| 178 | + def assert_appends_value(option, &block) | |
| 179 | + config = HoptoadNotifier::Configuration.new | |
| 180 | + original_values = config.send(option).dup | |
| 181 | + block ||= lambda do |config| | |
| 182 | + new_value = 'hello' | |
| 183 | + config.send(option) << new_value | |
| 184 | + new_value | |
| 143 | 185 | end |
| 186 | + new_value = block.call(config) | |
| 187 | + assert_same_elements original_values + [new_value], config.send(option) | |
| 188 | + end | |
| 189 | + | |
| 190 | + def assert_replaces(option, setter) | |
| 191 | + config = HoptoadNotifier::Configuration.new | |
| 192 | + new_value = 'hello' | |
| 193 | + config.send(setter, [new_value]) | |
| 194 | + assert_equal [new_value], config.send(option) | |
| 195 | + config.send(setter, new_value) | |
| 196 | + assert_equal [new_value], config.send(option) | |
| 144 | 197 | end |
| 198 | + | |
| 145 | 199 | end | ... | ... |
vendor/plugins/hoptoad_notifier/test/controller_test.rb
| ... | ... | @@ -1,366 +0,0 @@ |
| 1 | -require File.dirname(__FILE__) + '/helper' | |
| 2 | - | |
| 3 | -def expect_session_data_for(controller) | |
| 4 | - # NOTE: setting expectations on the controller is not a good idea here, | |
| 5 | - # because the controller is the unit we're trying to test. However, as all | |
| 6 | - # exception-related behavior is mixed into the controller itsef, we have | |
| 7 | - # little choice. Delegating notifier methods from the controller to a | |
| 8 | - # Sender could make this easier to maintain and test. | |
| 9 | - | |
| 10 | - @controller.expects(:send_to_hoptoad).with do |params| | |
| 11 | - assert params.respond_to?(:to_hash), "The notifier needs a hash" | |
| 12 | - notice = params[:notice] | |
| 13 | - assert_not_nil notice, "No notice passed to the notifier" | |
| 14 | - assert_not_nil notice[:session][:key], "No session key was set" | |
| 15 | - assert_not_nil notice[:session][:data], "No session data was set" | |
| 16 | - true | |
| 17 | - end | |
| 18 | - @controller.stubs(:rescue_action_in_public_without_hoptoad) | |
| 19 | -end | |
| 20 | - | |
| 21 | -def should_notify_normally | |
| 22 | - should "have inserted its methods into the controller" do | |
| 23 | - assert @controller.methods.include?("inform_hoptoad") | |
| 24 | - end | |
| 25 | - | |
| 26 | - should "prevent raises and send the error to hoptoad" do | |
| 27 | - @controller.expects(:notify_hoptoad) | |
| 28 | - @controller.expects(:rescue_action_in_public_without_hoptoad) | |
| 29 | - assert_nothing_raised do | |
| 30 | - request("do_raise") | |
| 31 | - end | |
| 32 | - end | |
| 33 | - | |
| 34 | - should "allow a non-raising action to complete" do | |
| 35 | - assert_nothing_raised do | |
| 36 | - request("do_not_raise") | |
| 37 | - end | |
| 38 | - end | |
| 39 | - | |
| 40 | - should "allow manual sending of exceptions" do | |
| 41 | - @controller.expects(:notify_hoptoad) | |
| 42 | - @controller.expects(:rescue_action_in_public_without_hoptoad).never | |
| 43 | - assert_nothing_raised do | |
| 44 | - request("manual_notify") | |
| 45 | - end | |
| 46 | - end | |
| 47 | - | |
| 48 | - should "disable manual sending of exceptions in a non-public (development or test) environment" do | |
| 49 | - @controller.stubs(:public_environment?).returns(false) | |
| 50 | - @controller.expects(:send_to_hoptoad).never | |
| 51 | - @controller.expects(:rescue_action_in_public_without_hoptoad).never | |
| 52 | - assert_nothing_raised do | |
| 53 | - request("manual_notify") | |
| 54 | - end | |
| 55 | - end | |
| 56 | - | |
| 57 | - should "send even ignored exceptions if told manually" do | |
| 58 | - @controller.expects(:notify_hoptoad) | |
| 59 | - @controller.expects(:rescue_action_in_public_without_hoptoad).never | |
| 60 | - assert_nothing_raised do | |
| 61 | - request("manual_notify_ignored") | |
| 62 | - end | |
| 63 | - end | |
| 64 | - | |
| 65 | - should "ignore default exceptions" do | |
| 66 | - @controller.expects(:notify_hoptoad).never | |
| 67 | - @controller.expects(:rescue_action_in_public_without_hoptoad) | |
| 68 | - assert_nothing_raised do | |
| 69 | - request("do_raise_ignored") | |
| 70 | - end | |
| 71 | - end | |
| 72 | - | |
| 73 | - should "filter non-serializable data" do | |
| 74 | - File.open(__FILE__) do |file| | |
| 75 | - assert_equal( {:ghi => "789"}, | |
| 76 | - @controller.send(:clean_non_serializable_data, :ghi => "789", :class => Class.new, :file => file) ) | |
| 77 | - end | |
| 78 | - end | |
| 79 | - | |
| 80 | - should "apply all params, environment and technical filters" do | |
| 81 | - params_hash = {:abc => 123} | |
| 82 | - environment_hash = {:def => 456} | |
| 83 | - backtrace_data = :backtrace_data | |
| 84 | - | |
| 85 | - raw_notice = {:request => {:params => params_hash}, | |
| 86 | - :environment => environment_hash, | |
| 87 | - :backtrace => backtrace_data} | |
| 88 | - | |
| 89 | - processed_notice = {:backtrace => :backtrace_data, | |
| 90 | - :request => {:params => :params_data}, | |
| 91 | - :environment => :environment_data} | |
| 92 | - | |
| 93 | - @controller.expects(:clean_hoptoad_backtrace).with(backtrace_data).returns(:backtrace_data) | |
| 94 | - @controller.expects(:clean_hoptoad_params).with(params_hash).returns(:params_data) | |
| 95 | - @controller.expects(:clean_hoptoad_environment).with(environment_hash).returns(:environment_data) | |
| 96 | - @controller.expects(:clean_non_serializable_data).with(processed_notice).returns(:serializable_data) | |
| 97 | - | |
| 98 | - assert_equal(:serializable_data, @controller.send(:clean_notice, raw_notice)) | |
| 99 | - end | |
| 100 | - | |
| 101 | - should "send session data to hoptoad when the session has @data" do | |
| 102 | - expect_session_data_for(@controller) | |
| 103 | - @request = ActionController::TestRequest.new | |
| 104 | - @request.action = 'do_raise' | |
| 105 | - @request.session.instance_variable_set("@data", { :message => 'Hello' }) | |
| 106 | - @response = ActionController::TestResponse.new | |
| 107 | - @controller.process(@request, @response) | |
| 108 | - end | |
| 109 | - | |
| 110 | - should "send session data to hoptoad when the session responds to to_hash" do | |
| 111 | - expect_session_data_for(@controller) | |
| 112 | - @request = ActionController::TestRequest.new | |
| 113 | - @request.action = 'do_raise' | |
| 114 | - @request.session.stubs(:to_hash).returns(:message => 'Hello') | |
| 115 | - @response = ActionController::TestResponse.new | |
| 116 | - @controller.process(@request, @response) | |
| 117 | - end | |
| 118 | -end | |
| 119 | - | |
| 120 | -def should_auto_include_catcher | |
| 121 | - should "auto-include for ApplicationController" do | |
| 122 | - assert ApplicationController.include?(HoptoadNotifier::Catcher) | |
| 123 | - end | |
| 124 | -end | |
| 125 | - | |
| 126 | -class ControllerTest < ActiveSupport::TestCase | |
| 127 | - context "Hoptoad inclusion" do | |
| 128 | - should "be able to occur even outside Rails controllers" do | |
| 129 | - assert_nothing_raised do | |
| 130 | - class MyHoptoad | |
| 131 | - include HoptoadNotifier::Catcher | |
| 132 | - end | |
| 133 | - end | |
| 134 | - my = MyHoptoad.new | |
| 135 | - assert my.respond_to?(:notify_hoptoad) | |
| 136 | - end | |
| 137 | - | |
| 138 | - context "when auto-included" do | |
| 139 | - setup do | |
| 140 | - class ::ApplicationController < ActionController::Base | |
| 141 | - end | |
| 142 | - | |
| 143 | - class ::AutoIncludeController < ::ApplicationController | |
| 144 | - include TestMethods | |
| 145 | - def rescue_action e | |
| 146 | - rescue_action_in_public e | |
| 147 | - end | |
| 148 | - end | |
| 149 | - | |
| 150 | - HoptoadNotifier.ignore_only = HoptoadNotifier::IGNORE_DEFAULT | |
| 151 | - @controller = ::AutoIncludeController.new | |
| 152 | - @controller.stubs(:public_environment?).returns(true) | |
| 153 | - @controller.stubs(:send_to_hoptoad) | |
| 154 | - | |
| 155 | - HoptoadNotifier.configure do |config| | |
| 156 | - config.api_key = "1234567890abcdef" | |
| 157 | - end | |
| 158 | - end | |
| 159 | - | |
| 160 | - context "when included through the configure block" do | |
| 161 | - should_auto_include_catcher | |
| 162 | - should_notify_normally | |
| 163 | - end | |
| 164 | - | |
| 165 | - context "when included both through configure and normally" do | |
| 166 | - setup do | |
| 167 | - class ::AutoIncludeController < ::ApplicationController | |
| 168 | - include HoptoadNotifier::Catcher | |
| 169 | - end | |
| 170 | - end | |
| 171 | - should_auto_include_catcher | |
| 172 | - should_notify_normally | |
| 173 | - end | |
| 174 | - end | |
| 175 | - end | |
| 176 | - | |
| 177 | - context "when the logger is overridden for an action" do | |
| 178 | - setup do | |
| 179 | - class ::IgnoreActionController < ::ActionController::Base | |
| 180 | - include TestMethods | |
| 181 | - include HoptoadNotifier::Catcher | |
| 182 | - def rescue_action e | |
| 183 | - rescue_action_in_public e | |
| 184 | - end | |
| 185 | - def logger | |
| 186 | - super unless action_name == "do_raise" | |
| 187 | - end | |
| 188 | - end | |
| 189 | - ::ActionController::Base.logger = Logger.new(STDOUT) | |
| 190 | - @controller = ::IgnoreActionController.new | |
| 191 | - @controller.stubs(:public_environment?).returns(true) | |
| 192 | - @controller.stubs(:rescue_action_in_public_without_hoptoad) | |
| 193 | - | |
| 194 | - # stubbing out Net::HTTP as well | |
| 195 | - @body = 'body' | |
| 196 | - @http = stub(:post => @response, :read_timeout= => nil, :open_timeout= => nil, :use_ssl= => nil) | |
| 197 | - Net::HTTP.stubs(:new).returns(@http) | |
| 198 | - HoptoadNotifier.port = nil | |
| 199 | - HoptoadNotifier.host = nil | |
| 200 | - HoptoadNotifier.proxy_host = nil | |
| 201 | - end | |
| 202 | - | |
| 203 | - should "work when action is called and request works" do | |
| 204 | - @response = stub(:body => @body, :class => Net::HTTPSuccess) | |
| 205 | - assert_nothing_raised do | |
| 206 | - request("do_raise") | |
| 207 | - end | |
| 208 | - end | |
| 209 | - | |
| 210 | - should "work when action is called and request doesn't work" do | |
| 211 | - @response = stub(:body => @body, :class => Net::HTTPError) | |
| 212 | - assert_nothing_raised do | |
| 213 | - request("do_raise") | |
| 214 | - end | |
| 215 | - end | |
| 216 | - | |
| 217 | - should "work when action is called and hoptoad times out" do | |
| 218 | - @http.stubs(:post).raises(TimeoutError) | |
| 219 | - assert_nothing_raised do | |
| 220 | - request("do_raise") | |
| 221 | - end | |
| 222 | - end | |
| 223 | - end | |
| 224 | - | |
| 225 | - context "The hoptoad test controller" do | |
| 226 | - setup do | |
| 227 | - @controller = ::HoptoadController.new | |
| 228 | - class ::HoptoadController | |
| 229 | - def rescue_action e | |
| 230 | - raise e | |
| 231 | - end | |
| 232 | - end | |
| 233 | - end | |
| 234 | - | |
| 235 | - context "with no notifier catcher" do | |
| 236 | - should "not prevent raises" do | |
| 237 | - assert_raises RuntimeError do | |
| 238 | - request("do_raise") | |
| 239 | - end | |
| 240 | - end | |
| 241 | - | |
| 242 | - should "allow a non-raising action to complete" do | |
| 243 | - assert_nothing_raised do | |
| 244 | - request("do_not_raise") | |
| 245 | - end | |
| 246 | - end | |
| 247 | - end | |
| 248 | - | |
| 249 | - context "with the notifier installed" do | |
| 250 | - setup do | |
| 251 | - class ::HoptoadController | |
| 252 | - include HoptoadNotifier::Catcher | |
| 253 | - def rescue_action e | |
| 254 | - rescue_action_in_public e | |
| 255 | - end | |
| 256 | - end | |
| 257 | - HoptoadNotifier.ignore_only = HoptoadNotifier::IGNORE_DEFAULT | |
| 258 | - @controller.stubs(:public_environment?).returns(true) | |
| 259 | - @controller.stubs(:send_to_hoptoad) | |
| 260 | - end | |
| 261 | - | |
| 262 | - should_notify_normally | |
| 263 | - | |
| 264 | - context "and configured to ignore additional exceptions" do | |
| 265 | - setup do | |
| 266 | - HoptoadNotifier.ignore << ActiveRecord::StatementInvalid | |
| 267 | - end | |
| 268 | - | |
| 269 | - should "still ignore default exceptions" do | |
| 270 | - @controller.expects(:notify_hoptoad).never | |
| 271 | - @controller.expects(:rescue_action_in_public_without_hoptoad) | |
| 272 | - assert_nothing_raised do | |
| 273 | - request("do_raise_ignored") | |
| 274 | - end | |
| 275 | - end | |
| 276 | - | |
| 277 | - should "ignore specified exceptions" do | |
| 278 | - @controller.expects(:notify_hoptoad).never | |
| 279 | - @controller.expects(:rescue_action_in_public_without_hoptoad) | |
| 280 | - assert_nothing_raised do | |
| 281 | - request("do_raise_not_ignored") | |
| 282 | - end | |
| 283 | - end | |
| 284 | - | |
| 285 | - should "not ignore unspecified, non-default exceptions" do | |
| 286 | - @controller.expects(:notify_hoptoad) | |
| 287 | - @controller.expects(:rescue_action_in_public_without_hoptoad) | |
| 288 | - assert_nothing_raised do | |
| 289 | - request("do_raise") | |
| 290 | - end | |
| 291 | - end | |
| 292 | - end | |
| 293 | - | |
| 294 | - context "and configured to ignore only certain exceptions" do | |
| 295 | - setup do | |
| 296 | - HoptoadNotifier.ignore_only = [ActiveRecord::StatementInvalid] | |
| 297 | - end | |
| 298 | - | |
| 299 | - should "no longer ignore default exceptions" do | |
| 300 | - @controller.expects(:notify_hoptoad) | |
| 301 | - @controller.expects(:rescue_action_in_public_without_hoptoad) | |
| 302 | - assert_nothing_raised do | |
| 303 | - request("do_raise_ignored") | |
| 304 | - end | |
| 305 | - end | |
| 306 | - | |
| 307 | - should "ignore specified exceptions" do | |
| 308 | - @controller.expects(:notify_hoptoad).never | |
| 309 | - @controller.expects(:rescue_action_in_public_without_hoptoad) | |
| 310 | - assert_nothing_raised do | |
| 311 | - request("do_raise_not_ignored") | |
| 312 | - end | |
| 313 | - end | |
| 314 | - | |
| 315 | - should "not ignore unspecified, non-default exceptions" do | |
| 316 | - @controller.expects(:notify_hoptoad) | |
| 317 | - @controller.expects(:rescue_action_in_public_without_hoptoad) | |
| 318 | - assert_nothing_raised do | |
| 319 | - request("do_raise") | |
| 320 | - end | |
| 321 | - end | |
| 322 | - end | |
| 323 | - | |
| 324 | - context "and configured to ignore certain user agents" do | |
| 325 | - setup do | |
| 326 | - HoptoadNotifier.ignore_user_agent << /Ignored/ | |
| 327 | - HoptoadNotifier.ignore_user_agent << 'IgnoredUserAgent' | |
| 328 | - end | |
| 329 | - | |
| 330 | - should "ignore exceptions when user agent is being ignored" do | |
| 331 | - @controller.expects(:notify_hoptoad).never | |
| 332 | - @controller.expects(:rescue_action_in_public_without_hoptoad) | |
| 333 | - assert_nothing_raised do | |
| 334 | - request("do_raise", :get, 'IgnoredUserAgent') | |
| 335 | - end | |
| 336 | - end | |
| 337 | - | |
| 338 | - should "ignore exceptions when user agent is being ignored (regexp)" do | |
| 339 | - HoptoadNotifier.ignore_user_agent_only = [/Ignored/] | |
| 340 | - @controller.expects(:notify_hoptoad).never | |
| 341 | - @controller.expects(:rescue_action_in_public_without_hoptoad) | |
| 342 | - assert_nothing_raised do | |
| 343 | - request("do_raise", :get, 'IgnoredUserAgent') | |
| 344 | - end | |
| 345 | - end | |
| 346 | - | |
| 347 | - should "ignore exceptions when user agent is being ignored (string)" do | |
| 348 | - HoptoadNotifier.ignore_user_agent_only = ['IgnoredUserAgent'] | |
| 349 | - @controller.expects(:notify_hoptoad).never | |
| 350 | - @controller.expects(:rescue_action_in_public_without_hoptoad) | |
| 351 | - assert_nothing_raised do | |
| 352 | - request("do_raise", :get, 'IgnoredUserAgent') | |
| 353 | - end | |
| 354 | - end | |
| 355 | - | |
| 356 | - should "not ignore exceptions when user agent is not being ignored" do | |
| 357 | - @controller.expects(:notify_hoptoad) | |
| 358 | - @controller.expects(:rescue_action_in_public_without_hoptoad) | |
| 359 | - assert_nothing_raised do | |
| 360 | - request("do_raise") | |
| 361 | - end | |
| 362 | - end | |
| 363 | - end | |
| 364 | - end | |
| 365 | - end | |
| 366 | -end |
vendor/plugins/hoptoad_notifier/test/helper.rb
| 1 | 1 | require 'test/unit' |
| 2 | 2 | require 'rubygems' |
| 3 | -require 'mocha' | |
| 4 | -gem 'thoughtbot-shoulda', ">= 2.0.0" | |
| 3 | + | |
| 4 | +gem 'jferris-mocha', '0.9.5.0.1241126838' | |
| 5 | + | |
| 5 | 6 | require 'shoulda' |
| 7 | +require 'mocha' | |
| 6 | 8 | |
| 7 | 9 | $LOAD_PATH << File.join(File.dirname(__FILE__), *%w[.. vendor ginger lib]) |
| 8 | 10 | require 'ginger' |
| ... | ... | @@ -12,13 +14,10 @@ require 'action_controller/test_process' |
| 12 | 14 | require 'active_record' |
| 13 | 15 | require 'active_record/base' |
| 14 | 16 | require 'active_support' |
| 15 | -require 'active_support/test_case' | |
| 17 | +require 'nokogiri' | |
| 16 | 18 | |
| 17 | 19 | require File.join(File.dirname(__FILE__), "..", "lib", "hoptoad_notifier") |
| 18 | 20 | |
| 19 | -RAILS_ROOT = File.join( File.dirname(__FILE__), "rails_root" ) | |
| 20 | -RAILS_ENV = "test" | |
| 21 | - | |
| 22 | 21 | begin require 'redgreen'; rescue LoadError; end |
| 23 | 22 | |
| 24 | 23 | module TestMethods |
| ... | ... | @@ -57,10 +56,183 @@ class HoptoadController < ActionController::Base |
| 57 | 56 | include TestMethods |
| 58 | 57 | end |
| 59 | 58 | |
| 60 | -def request(action = nil, method = :get, user_agent = nil) | |
| 61 | - @request = ActionController::TestRequest.new | |
| 62 | - @request.action = action ? action.to_s : "" | |
| 63 | - @request.user_agent = user_agent unless user_agent.nil? | |
| 64 | - @response = ActionController::TestResponse.new | |
| 65 | - @controller.process(@request, @response) | |
| 59 | +class Test::Unit::TestCase | |
| 60 | + def request(action = nil, method = :get, user_agent = nil, params = {}) | |
| 61 | + @request = ActionController::TestRequest.new | |
| 62 | + @request.action = action ? action.to_s : "" | |
| 63 | + | |
| 64 | + if user_agent | |
| 65 | + if @request.respond_to?(:user_agent=) | |
| 66 | + @request.user_agent = user_agent | |
| 67 | + else | |
| 68 | + @request.env["HTTP_USER_AGENT"] = user_agent | |
| 69 | + end | |
| 70 | + end | |
| 71 | + @request.query_parameters = @request.query_parameters.merge(params) | |
| 72 | + @response = ActionController::TestResponse.new | |
| 73 | + @controller.process(@request, @response) | |
| 74 | + end | |
| 75 | + | |
| 76 | + # Borrowed from ActiveSupport 2.3.2 | |
| 77 | + def assert_difference(expression, difference = 1, message = nil, &block) | |
| 78 | + b = block.send(:binding) | |
| 79 | + exps = Array.wrap(expression) | |
| 80 | + before = exps.map { |e| eval(e, b) } | |
| 81 | + | |
| 82 | + yield | |
| 83 | + | |
| 84 | + exps.each_with_index do |e, i| | |
| 85 | + error = "#{e.inspect} didn't change by #{difference}" | |
| 86 | + error = "#{message}.\n#{error}" if message | |
| 87 | + assert_equal(before[i] + difference, eval(e, b), error) | |
| 88 | + end | |
| 89 | + end | |
| 90 | + | |
| 91 | + def assert_no_difference(expression, message = nil, &block) | |
| 92 | + assert_difference expression, 0, message, &block | |
| 93 | + end | |
| 94 | + | |
| 95 | + def stub_sender | |
| 96 | + stub('sender', :send_to_hoptoad => nil) | |
| 97 | + end | |
| 98 | + | |
| 99 | + def stub_sender! | |
| 100 | + HoptoadNotifier.sender = stub_sender | |
| 101 | + end | |
| 102 | + | |
| 103 | + def stub_notice | |
| 104 | + stub('notice', :to_xml => 'some yaml', :ignore? => false) | |
| 105 | + end | |
| 106 | + | |
| 107 | + def stub_notice! | |
| 108 | + returning stub_notice do |notice| | |
| 109 | + HoptoadNotifier::Notice.stubs(:new => notice) | |
| 110 | + end | |
| 111 | + end | |
| 112 | + | |
| 113 | + def create_dummy | |
| 114 | + HoptoadNotifier::DummySender.new | |
| 115 | + end | |
| 116 | + | |
| 117 | + def reset_config | |
| 118 | + HoptoadNotifier.configuration = nil | |
| 119 | + HoptoadNotifier.configure do |config| | |
| 120 | + config.api_key = 'abc123' | |
| 121 | + end | |
| 122 | + end | |
| 123 | + | |
| 124 | + def clear_backtrace_filters | |
| 125 | + HoptoadNotifier.configuration.backtrace_filters.clear | |
| 126 | + end | |
| 127 | + | |
| 128 | + def build_exception | |
| 129 | + raise | |
| 130 | + rescue => caught_exception | |
| 131 | + caught_exception | |
| 132 | + end | |
| 133 | + | |
| 134 | + def build_notice_data(exception = nil) | |
| 135 | + exception ||= build_exception | |
| 136 | + { | |
| 137 | + :api_key => 'abc123', | |
| 138 | + :error_class => exception.class.name, | |
| 139 | + :error_message => "#{exception.class.name}: #{exception.message}", | |
| 140 | + :backtrace => exception.backtrace, | |
| 141 | + :environment => { 'PATH' => '/bin', 'REQUEST_URI' => '/users/1' }, | |
| 142 | + :request => { | |
| 143 | + :params => { 'controller' => 'users', 'action' => 'show', 'id' => '1' }, | |
| 144 | + :rails_root => '/path/to/application', | |
| 145 | + :url => "http://test.host/users/1" | |
| 146 | + }, | |
| 147 | + :session => { | |
| 148 | + :key => '123abc', | |
| 149 | + :data => { 'user_id' => '5', 'flash' => { 'notice' => 'Logged in successfully' } } | |
| 150 | + } | |
| 151 | + } | |
| 152 | + end | |
| 153 | + | |
| 154 | + def assert_caught_and_sent | |
| 155 | + assert !HoptoadNotifier.sender.collected.empty? | |
| 156 | + end | |
| 157 | + | |
| 158 | + def assert_caught_and_not_sent | |
| 159 | + assert HoptoadNotifier.sender.collected.empty? | |
| 160 | + end | |
| 161 | + | |
| 162 | + def assert_array_starts_with(expected, actual) | |
| 163 | + assert_respond_to actual, :to_ary | |
| 164 | + array = actual.to_ary.reverse | |
| 165 | + expected.reverse.each_with_index do |value, i| | |
| 166 | + assert_equal value, array[i] | |
| 167 | + end | |
| 168 | + end | |
| 169 | + | |
| 170 | + def assert_valid_node(document, xpath, content) | |
| 171 | + nodes = document.xpath(xpath) | |
| 172 | + assert nodes.any?{|node| node.content == content }, | |
| 173 | + "Expected xpath #{xpath} to have content #{content}, " + | |
| 174 | + "but found #{nodes.map { |n| n.content }} in #{nodes.size} matching nodes." + | |
| 175 | + "Document:\n#{document.to_s}" | |
| 176 | + end | |
| 177 | +end | |
| 178 | + | |
| 179 | +module DefinesConstants | |
| 180 | + def setup | |
| 181 | + @defined_constants = [] | |
| 182 | + end | |
| 183 | + | |
| 184 | + def teardown | |
| 185 | + @defined_constants.each do |constant| | |
| 186 | + Object.__send__(:remove_const, constant) | |
| 187 | + end | |
| 188 | + end | |
| 189 | + | |
| 190 | + def define_constant(name, value) | |
| 191 | + Object.const_set(name, value) | |
| 192 | + @defined_constants << name | |
| 193 | + end | |
| 194 | +end | |
| 195 | + | |
| 196 | +# Also stolen from AS 2.3.2 | |
| 197 | +class Array | |
| 198 | + # Wraps the object in an Array unless it's an Array. Converts the | |
| 199 | + # object to an Array using #to_ary if it implements that. | |
| 200 | + def self.wrap(object) | |
| 201 | + case object | |
| 202 | + when nil | |
| 203 | + [] | |
| 204 | + when self | |
| 205 | + object | |
| 206 | + else | |
| 207 | + if object.respond_to?(:to_ary) | |
| 208 | + object.to_ary | |
| 209 | + else | |
| 210 | + [object] | |
| 211 | + end | |
| 212 | + end | |
| 213 | + end | |
| 214 | + | |
| 66 | 215 | end |
| 216 | + | |
| 217 | +class CollectingSender | |
| 218 | + attr_reader :collected | |
| 219 | + | |
| 220 | + def initialize | |
| 221 | + @collected = [] | |
| 222 | + end | |
| 223 | + | |
| 224 | + def send_to_hoptoad(data) | |
| 225 | + @collected << data | |
| 226 | + end | |
| 227 | +end | |
| 228 | + | |
| 229 | +class FakeLogger | |
| 230 | + def info(*args); end | |
| 231 | + def debug(*args); end | |
| 232 | + def warn(*args); end | |
| 233 | + def error(*args); end | |
| 234 | + def fatal(*args); end | |
| 235 | +end | |
| 236 | + | |
| 237 | +RAILS_DEFAULT_LOGGER = FakeLogger.new | |
| 238 | + | ... | ... |
| ... | ... | @@ -0,0 +1,76 @@ |
| 1 | +<?xml version="1.0"?> | |
| 2 | +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> | |
| 3 | + | |
| 4 | + <xs:element name="notice"> | |
| 5 | + <xs:complexType> | |
| 6 | + <xs:all> | |
| 7 | + <xs:element name="api-key" type="xs:string"/> | |
| 8 | + <xs:element name="notifier" type="notifier"/> | |
| 9 | + <xs:element name="error" type="error"/> | |
| 10 | + <xs:element name="request" type="request" minOccurs="0"/> | |
| 11 | + <xs:element name="server-environment" type="serverEnvironment"/> | |
| 12 | + </xs:all> | |
| 13 | + <xs:attribute name="version" type="xs:string" use="required"/> | |
| 14 | + </xs:complexType> | |
| 15 | + </xs:element> | |
| 16 | + | |
| 17 | + <xs:complexType name="notifier"> | |
| 18 | + <xs:all> | |
| 19 | + <xs:element name="name" type="xs:string"/> | |
| 20 | + <xs:element name="version" type="xs:string"/> | |
| 21 | + <xs:element name="url" type="xs:string"/> | |
| 22 | + </xs:all> | |
| 23 | + </xs:complexType> | |
| 24 | + | |
| 25 | + <xs:complexType name="error"> | |
| 26 | + <xs:all> | |
| 27 | + <xs:element name="class" type="xs:string"/> | |
| 28 | + <xs:element name="message" type="xs:string" minOccurs="0"/> | |
| 29 | + <xs:element name="backtrace" type="backtrace"/> | |
| 30 | + </xs:all> | |
| 31 | + </xs:complexType> | |
| 32 | + | |
| 33 | + <xs:complexType name="backtrace"> | |
| 34 | + <xs:sequence> | |
| 35 | + <xs:element name="line" maxOccurs="unbounded"> | |
| 36 | + <xs:complexType> | |
| 37 | + <xs:attribute name="file" type="xs:string" use="required"/> | |
| 38 | + <xs:attribute name="number" type="xs:string" use="required"/> | |
| 39 | + <xs:attribute name="method" type="xs:string" use="optional"/> | |
| 40 | + </xs:complexType> | |
| 41 | + </xs:element> | |
| 42 | + </xs:sequence> | |
| 43 | + </xs:complexType> | |
| 44 | + | |
| 45 | + <xs:complexType name="request"> | |
| 46 | + <xs:all> | |
| 47 | + <xs:element name="url" type="xs:string"/> | |
| 48 | + <xs:element name="component" type="xs:string"/> | |
| 49 | + <xs:element name="action" type="xs:string" minOccurs="0"/> | |
| 50 | + <xs:element name="params" type="varList" minOccurs="0"/> | |
| 51 | + <xs:element name="session" type="varList" minOccurs="0"/> | |
| 52 | + <xs:element name="cgi-data" type="varList" minOccurs="0"/> | |
| 53 | + </xs:all> | |
| 54 | + </xs:complexType> | |
| 55 | + | |
| 56 | + <xs:complexType name="varList"> | |
| 57 | + <xs:sequence> | |
| 58 | + <xs:element name="var" type="var" maxOccurs="unbounded"/> | |
| 59 | + </xs:sequence> | |
| 60 | + </xs:complexType> | |
| 61 | + | |
| 62 | + <xs:complexType name="var" mixed="true"> | |
| 63 | + <xs:sequence> | |
| 64 | + <xs:element name="var" type="var" minOccurs="0" maxOccurs="unbounded"/> | |
| 65 | + </xs:sequence> | |
| 66 | + <xs:attribute name="key" type="xs:string" use="required"/> | |
| 67 | + </xs:complexType> | |
| 68 | + | |
| 69 | + <xs:complexType name="serverEnvironment"> | |
| 70 | + <xs:sequence> | |
| 71 | + <xs:element name="project-root" type="xs:string" minOccurs="0"/> | |
| 72 | + <xs:element name="environment-name" type="xs:string"/> | |
| 73 | + </xs:sequence> | |
| 74 | + </xs:complexType> | |
| 75 | + | |
| 76 | +</xs:schema> | ... | ... |
vendor/plugins/hoptoad_notifier/test/hoptoad_tasks_test.rb
| ... | ... | @@ -6,7 +6,7 @@ require 'fakeweb' |
| 6 | 6 | |
| 7 | 7 | FakeWeb.allow_net_connect = false |
| 8 | 8 | |
| 9 | -class HoptoadTasksTest < ActiveSupport::TestCase | |
| 9 | +class HoptoadTasksTest < Test::Unit::TestCase | |
| 10 | 10 | def successful_response(body = "") |
| 11 | 11 | response = Net::HTTPSuccess.new('1.2', '200', 'OK') |
| 12 | 12 | response.stubs(:body).returns(body) |
| ... | ... | @@ -50,7 +50,7 @@ class HoptoadTasksTest < ActiveSupport::TestCase |
| 50 | 50 | |
| 51 | 51 | before_should "use the project api key" do |
| 52 | 52 | Net::HTTP.expects(:post_form). |
| 53 | - with(kind_of(URI), has_entries(:api_key => "1234123412341234")). | |
| 53 | + with(kind_of(URI), has_entries('api_key' => "1234123412341234")). | |
| 54 | 54 | returns(successful_response) |
| 55 | 55 | end |
| 56 | 56 | |
| ... | ... | @@ -69,6 +69,13 @@ class HoptoadTasksTest < ActiveSupport::TestCase |
| 69 | 69 | end |
| 70 | 70 | end |
| 71 | 71 | |
| 72 | + before_should "use the :api_key param if it's passed in." do | |
| 73 | + @options[:api_key] = "value" | |
| 74 | + Net::HTTP.expects(:post_form). | |
| 75 | + with(kind_of(URI), has_entries("api_key" => "value")). | |
| 76 | + returns(successful_response) | |
| 77 | + end | |
| 78 | + | |
| 72 | 79 | before_should "puts the response body on success" do |
| 73 | 80 | HoptoadTasks.expects(:puts).with("body") |
| 74 | 81 | Net::HTTP.expects(:post_form).with(any_parameters).returns(successful_response('body')) | ... | ... |
| ... | ... | @@ -0,0 +1,85 @@ |
| 1 | +require File.dirname(__FILE__) + '/helper' | |
| 2 | + | |
| 3 | +class LoggerTest < Test::Unit::TestCase | |
| 4 | + def stub_http(response, body = nil) | |
| 5 | + response.stubs(:body => body) if body | |
| 6 | + @http = stub(:post => response, | |
| 7 | + :read_timeout= => nil, | |
| 8 | + :open_timeout= => nil, | |
| 9 | + :use_ssl= => nil) | |
| 10 | + Net::HTTP.stubs(:new).returns(@http) | |
| 11 | + end | |
| 12 | + | |
| 13 | + def send_notice | |
| 14 | + HoptoadNotifier.sender.send_to_hoptoad('data') | |
| 15 | + end | |
| 16 | + | |
| 17 | + def stub_verbose_log | |
| 18 | + HoptoadNotifier.stubs(:write_verbose_log) | |
| 19 | + end | |
| 20 | + | |
| 21 | + def assert_logged(expected) | |
| 22 | + assert_received(HoptoadNotifier, :write_verbose_log) do |expect| | |
| 23 | + expect.with {|actual| actual =~ expected } | |
| 24 | + end | |
| 25 | + end | |
| 26 | + | |
| 27 | + def assert_not_logged(expected) | |
| 28 | + assert_received(HoptoadNotifier, :write_verbose_log) do |expect| | |
| 29 | + expect.with {|actual| actual =~ expected }.never | |
| 30 | + end | |
| 31 | + end | |
| 32 | + | |
| 33 | + def configure | |
| 34 | + HoptoadNotifier.configure { |config| } | |
| 35 | + end | |
| 36 | + | |
| 37 | + should "report that notifier is ready when configured" do | |
| 38 | + stub_verbose_log | |
| 39 | + configure | |
| 40 | + assert_logged /Notifier (.*) ready/ | |
| 41 | + end | |
| 42 | + | |
| 43 | + should "not report that notifier is ready when internally configured" do | |
| 44 | + stub_verbose_log | |
| 45 | + HoptoadNotifier.configure(true) { |config | } | |
| 46 | + assert_not_logged /.*/ | |
| 47 | + end | |
| 48 | + | |
| 49 | + should "print environment info a successful notification without a body" do | |
| 50 | + reset_config | |
| 51 | + stub_verbose_log | |
| 52 | + stub_http(Net::HTTPSuccess) | |
| 53 | + send_notice | |
| 54 | + assert_logged /Environment Info:/ | |
| 55 | + assert_not_logged /Response from Hoptoad:/ | |
| 56 | + end | |
| 57 | + | |
| 58 | + should "print environment info on a failed notification without a body" do | |
| 59 | + reset_config | |
| 60 | + stub_verbose_log | |
| 61 | + stub_http(Net::HTTPError) | |
| 62 | + send_notice | |
| 63 | + assert_logged /Environment Info:/ | |
| 64 | + assert_not_logged /Response from Hoptoad:/ | |
| 65 | + end | |
| 66 | + | |
| 67 | + should "print environment info and response on a success with a body" do | |
| 68 | + reset_config | |
| 69 | + stub_verbose_log | |
| 70 | + stub_http(Net::HTTPSuccess, 'test') | |
| 71 | + send_notice | |
| 72 | + assert_logged /Environment Info:/ | |
| 73 | + assert_logged /Response from Hoptoad:/ | |
| 74 | + end | |
| 75 | + | |
| 76 | + should "print environment info and response on a failure with a body" do | |
| 77 | + reset_config | |
| 78 | + stub_verbose_log | |
| 79 | + stub_http(Net::HTTPError, 'test') | |
| 80 | + send_notice | |
| 81 | + assert_logged /Environment Info:/ | |
| 82 | + assert_logged /Response from Hoptoad:/ | |
| 83 | + end | |
| 84 | + | |
| 85 | +end | ... | ... |
| ... | ... | @@ -0,0 +1,363 @@ |
| 1 | +require File.dirname(__FILE__) + '/helper' | |
| 2 | + | |
| 3 | +class NoticeTest < Test::Unit::TestCase | |
| 4 | + | |
| 5 | + include DefinesConstants | |
| 6 | + | |
| 7 | + def configure | |
| 8 | + returning HoptoadNotifier::Configuration.new do |config| | |
| 9 | + config.api_key = 'abc123def456' | |
| 10 | + end | |
| 11 | + end | |
| 12 | + | |
| 13 | + def build_notice(args = {}) | |
| 14 | + configuration = args.delete(:configuration) || configure | |
| 15 | + HoptoadNotifier::Notice.new(configuration.merge(args)) | |
| 16 | + end | |
| 17 | + | |
| 18 | + def stub_request(attrs = {}) | |
| 19 | + stub('request', { :parameters => { 'one' => 'two' }, | |
| 20 | + :protocol => 'http', | |
| 21 | + :host => 'some.host', | |
| 22 | + :request_uri => '/some/uri', | |
| 23 | + :session => { :to_hash => { 'a' => 'b' } }, | |
| 24 | + :env => { 'three' => 'four' } }.update(attrs)) | |
| 25 | + end | |
| 26 | + | |
| 27 | + should "set the api key" do | |
| 28 | + api_key = 'key' | |
| 29 | + notice = build_notice(:api_key => api_key) | |
| 30 | + assert_equal api_key, notice.api_key | |
| 31 | + end | |
| 32 | + | |
| 33 | + should "accept a project root" do | |
| 34 | + project_root = '/path/to/project' | |
| 35 | + notice = build_notice(:project_root => project_root) | |
| 36 | + assert_equal project_root, notice.project_root | |
| 37 | + end | |
| 38 | + | |
| 39 | + should "accept a component" do | |
| 40 | + assert_equal 'users_controller', build_notice(:component => 'users_controller').controller | |
| 41 | + end | |
| 42 | + | |
| 43 | + should "alias the component as controller" do | |
| 44 | + assert_equal 'users_controller', build_notice(:controller => 'users_controller').component | |
| 45 | + assert_equal 'users_controller', build_notice(:component => 'users_controller').controller | |
| 46 | + end | |
| 47 | + | |
| 48 | + should "accept a action" do | |
| 49 | + assert_equal 'index', build_notice(:action => 'index').action | |
| 50 | + end | |
| 51 | + | |
| 52 | + should "accept a url" do | |
| 53 | + url = 'http://some.host/uri' | |
| 54 | + notice = build_notice(:url => url) | |
| 55 | + assert_equal url, notice.url | |
| 56 | + end | |
| 57 | + | |
| 58 | + should "accept a backtrace from an exception or hash" do | |
| 59 | + array = ["user.rb:34:in `crazy'"] | |
| 60 | + exception = build_exception | |
| 61 | + exception.set_backtrace array | |
| 62 | + backtrace = HoptoadNotifier::Backtrace.parse(array) | |
| 63 | + notice_from_exception = build_notice(:exception => exception) | |
| 64 | + | |
| 65 | + | |
| 66 | + assert_equal notice_from_exception.backtrace, | |
| 67 | + backtrace, | |
| 68 | + "backtrace was not correctly set from an exception" | |
| 69 | + | |
| 70 | + notice_from_hash = build_notice(:backtrace => array) | |
| 71 | + assert_equal notice_from_hash.backtrace, | |
| 72 | + backtrace, | |
| 73 | + "backtrace was not correctly set from a hash" | |
| 74 | + end | |
| 75 | + | |
| 76 | + should "set the error class from an exception or hash" do | |
| 77 | + assert_accepts_exception_attribute :error_class do |exception| | |
| 78 | + exception.class.name | |
| 79 | + end | |
| 80 | + end | |
| 81 | + | |
| 82 | + should "set the error message from an exception or hash" do | |
| 83 | + assert_accepts_exception_attribute :error_message do |exception| | |
| 84 | + "#{exception.class.name}: #{exception.message}" | |
| 85 | + end | |
| 86 | + end | |
| 87 | + | |
| 88 | + should "accept parameters from a request or hash" do | |
| 89 | + parameters = { 'one' => 'two' } | |
| 90 | + notice_from_hash = build_notice(:parameters => parameters) | |
| 91 | + assert_equal notice_from_hash.parameters, parameters | |
| 92 | + end | |
| 93 | + | |
| 94 | + should "accept session data from a session[:data] hash" do | |
| 95 | + data = { 'one' => 'two' } | |
| 96 | + notice = build_notice(:session => { :data => data }) | |
| 97 | + assert_equal data, notice.session_data | |
| 98 | + end | |
| 99 | + | |
| 100 | + should "accept session data from a session_data hash" do | |
| 101 | + data = { 'one' => 'two' } | |
| 102 | + notice = build_notice(:session_data => data) | |
| 103 | + assert_equal data, notice.session_data | |
| 104 | + end | |
| 105 | + | |
| 106 | + should "accept an environment name" do | |
| 107 | + assert_equal 'development', build_notice(:environment_name => 'development').environment_name | |
| 108 | + end | |
| 109 | + | |
| 110 | + should "accept CGI data from a hash" do | |
| 111 | + data = { 'string' => 'value' } | |
| 112 | + notice = build_notice(:cgi_data => data) | |
| 113 | + assert_equal data, notice.cgi_data, "should take CGI data from a hash" | |
| 114 | + end | |
| 115 | + | |
| 116 | + should "accept notifier information" do | |
| 117 | + params = { :notifier_name => 'a name for a notifier', | |
| 118 | + :notifier_version => '1.0.5', | |
| 119 | + :notifier_url => 'http://notifiers.r.us/download' } | |
| 120 | + notice = build_notice(params) | |
| 121 | + assert_equal params[:notifier_name], notice.notifier_name | |
| 122 | + assert_equal params[:notifier_version], notice.notifier_version | |
| 123 | + assert_equal params[:notifier_url], notice.notifier_url | |
| 124 | + end | |
| 125 | + | |
| 126 | + should "set sensible defaults without an exception" do | |
| 127 | + backtrace = HoptoadNotifier::Backtrace.parse(caller) | |
| 128 | + notice = build_notice | |
| 129 | + | |
| 130 | + assert_equal 'Notification', notice.error_message | |
| 131 | + assert_array_starts_with backtrace.lines, notice.backtrace.lines | |
| 132 | + assert_equal({}, notice.parameters) | |
| 133 | + assert_equal({}, notice.session_data) | |
| 134 | + end | |
| 135 | + | |
| 136 | + should "use the caller as the backtrace for an exception without a backtrace" do | |
| 137 | + backtrace = HoptoadNotifier::Backtrace.parse(caller) | |
| 138 | + notice = build_notice(:exception => StandardError.new('error'), :backtrace => nil) | |
| 139 | + | |
| 140 | + assert_array_starts_with backtrace.lines, notice.backtrace.lines | |
| 141 | + end | |
| 142 | + | |
| 143 | + should "convert unserializable objects to strings" do | |
| 144 | + assert_serializes_hash(:parameters) | |
| 145 | + assert_serializes_hash(:cgi_data) | |
| 146 | + assert_serializes_hash(:session_data) | |
| 147 | + end | |
| 148 | + | |
| 149 | + should "filter parameters" do | |
| 150 | + assert_filters_hash(:parameters) | |
| 151 | + end | |
| 152 | + | |
| 153 | + should "filter cgi data" do | |
| 154 | + assert_filters_hash(:cgi_data) | |
| 155 | + end | |
| 156 | + | |
| 157 | + context "a Notice turned into XML" do | |
| 158 | + setup do | |
| 159 | + HoptoadNotifier.configure do |config| | |
| 160 | + config.api_key = "1234567890" | |
| 161 | + end | |
| 162 | + | |
| 163 | + @exception = build_exception | |
| 164 | + | |
| 165 | + @notice = build_notice({ | |
| 166 | + :notifier_name => 'a name', | |
| 167 | + :notifier_version => '1.2.3', | |
| 168 | + :notifier_url => 'http://some.url/path', | |
| 169 | + :exception => @exception, | |
| 170 | + :controller => "controller", | |
| 171 | + :action => "action", | |
| 172 | + :url => "http://url.com", | |
| 173 | + :parameters => { "paramskey" => "paramsvalue", | |
| 174 | + "nestparentkey" => { "nestkey" => "nestvalue" } }, | |
| 175 | + :session_data => { "sessionkey" => "sessionvalue" }, | |
| 176 | + :cgi_data => { "cgikey" => "cgivalue" }, | |
| 177 | + :project_root => "RAILS_ROOT", | |
| 178 | + :environment_name => "RAILS_ENV" | |
| 179 | + }) | |
| 180 | + | |
| 181 | + @xml = @notice.to_xml | |
| 182 | + | |
| 183 | + @document = Nokogiri::XML::Document.parse(@xml) | |
| 184 | + end | |
| 185 | + | |
| 186 | + should "validate against the XML schema" do | |
| 187 | + assert_valid_notice_document @document | |
| 188 | + end | |
| 189 | + | |
| 190 | + should "serialize a Notice to XML when sent #to_xml" do | |
| 191 | + assert_valid_node(@document, "//api-key", @notice.api_key) | |
| 192 | + | |
| 193 | + assert_valid_node(@document, "//notifier/name", @notice.notifier_name) | |
| 194 | + assert_valid_node(@document, "//notifier/version", @notice.notifier_version) | |
| 195 | + assert_valid_node(@document, "//notifier/url", @notice.notifier_url) | |
| 196 | + | |
| 197 | + assert_valid_node(@document, "//error/class", @notice.error_class) | |
| 198 | + assert_valid_node(@document, "//error/message", @notice.error_message) | |
| 199 | + | |
| 200 | + assert_valid_node(@document, "//error/backtrace/line/@number", @notice.backtrace.lines.first.number) | |
| 201 | + assert_valid_node(@document, "//error/backtrace/line/@file", @notice.backtrace.lines.first.file) | |
| 202 | + assert_valid_node(@document, "//error/backtrace/line/@method", @notice.backtrace.lines.first.method) | |
| 203 | + | |
| 204 | + assert_valid_node(@document, "//request/url", @notice.url) | |
| 205 | + assert_valid_node(@document, "//request/component", @notice.controller) | |
| 206 | + assert_valid_node(@document, "//request/action", @notice.action) | |
| 207 | + | |
| 208 | + assert_valid_node(@document, "//request/params/var/@key", "paramskey") | |
| 209 | + assert_valid_node(@document, "//request/params/var", "paramsvalue") | |
| 210 | + assert_valid_node(@document, "//request/params/var/@key", "nestparentkey") | |
| 211 | + assert_valid_node(@document, "//request/params/var/var/@key", "nestkey") | |
| 212 | + assert_valid_node(@document, "//request/params/var/var", "nestvalue") | |
| 213 | + assert_valid_node(@document, "//request/session/var/@key", "sessionkey") | |
| 214 | + assert_valid_node(@document, "//request/session/var", "sessionvalue") | |
| 215 | + assert_valid_node(@document, "//request/cgi-data/var/@key", "cgikey") | |
| 216 | + assert_valid_node(@document, "//request/cgi-data/var", "cgivalue") | |
| 217 | + | |
| 218 | + assert_valid_node(@document, "//server-environment/project-root", "RAILS_ROOT") | |
| 219 | + assert_valid_node(@document, "//server-environment/environment-name", "RAILS_ENV") | |
| 220 | + end | |
| 221 | + end | |
| 222 | + | |
| 223 | + should "not send empty request data" do | |
| 224 | + notice = build_notice | |
| 225 | + assert_nil notice.url | |
| 226 | + assert_nil notice.controller | |
| 227 | + assert_nil notice.action | |
| 228 | + | |
| 229 | + xml = notice.to_xml | |
| 230 | + document = Nokogiri::XML.parse(xml) | |
| 231 | + assert_nil document.at('//request/url') | |
| 232 | + assert_nil document.at('//request/component') | |
| 233 | + assert_nil document.at('//request/action') | |
| 234 | + | |
| 235 | + assert_valid_notice_document document | |
| 236 | + end | |
| 237 | + | |
| 238 | + %w(url controller action).each do |var| | |
| 239 | + should "send a request if #{var} is present" do | |
| 240 | + notice = build_notice(var.to_sym => 'value') | |
| 241 | + xml = notice.to_xml | |
| 242 | + document = Nokogiri::XML.parse(xml) | |
| 243 | + assert_not_nil document.at('//request') | |
| 244 | + end | |
| 245 | + end | |
| 246 | + | |
| 247 | + %w(parameters cgi_data session_data).each do |var| | |
| 248 | + should "send a request if #{var} is present" do | |
| 249 | + notice = build_notice(var.to_sym => { 'key' => 'value' }) | |
| 250 | + xml = notice.to_xml | |
| 251 | + document = Nokogiri::XML.parse(xml) | |
| 252 | + assert_not_nil document.at('//request') | |
| 253 | + end | |
| 254 | + end | |
| 255 | + | |
| 256 | + should "not ignore an exception not matching ignore filters" do | |
| 257 | + notice = build_notice(:error_class => 'ArgumentError', | |
| 258 | + :ignore => ['Argument'], | |
| 259 | + :ignore_by_filters => [lambda { |notice| false }]) | |
| 260 | + assert !notice.ignore? | |
| 261 | + end | |
| 262 | + | |
| 263 | + should "ignore an exception with a matching error class" do | |
| 264 | + notice = build_notice(:error_class => 'ArgumentError', | |
| 265 | + :ignore => [ArgumentError]) | |
| 266 | + assert notice.ignore? | |
| 267 | + end | |
| 268 | + | |
| 269 | + should "ignore an exception with a matching error class name" do | |
| 270 | + notice = build_notice(:error_class => 'ArgumentError', | |
| 271 | + :ignore => ['ArgumentError']) | |
| 272 | + assert notice.ignore? | |
| 273 | + end | |
| 274 | + | |
| 275 | + should "ignore an exception with a matching filter" do | |
| 276 | + filter = lambda {|notice| notice.error_class == 'ArgumentError' } | |
| 277 | + notice = build_notice(:error_class => 'ArgumentError', | |
| 278 | + :ignore_by_filters => [filter]) | |
| 279 | + assert notice.ignore? | |
| 280 | + end | |
| 281 | + | |
| 282 | + should "not raise without an ignore list" do | |
| 283 | + notice = build_notice(:ignore => nil, :ignore_by_filters => nil) | |
| 284 | + assert_nothing_raised do | |
| 285 | + notice.ignore? | |
| 286 | + end | |
| 287 | + end | |
| 288 | + | |
| 289 | + should "act like a hash" do | |
| 290 | + notice = build_notice(:error_message => 'some message') | |
| 291 | + assert_equal notice.error_message, notice[:error_message] | |
| 292 | + end | |
| 293 | + | |
| 294 | + should "return params on notice[:request][:params]" do | |
| 295 | + params = { 'one' => 'two' } | |
| 296 | + notice = build_notice(:parameters => params) | |
| 297 | + assert_equal params, notice[:request][:params] | |
| 298 | + end | |
| 299 | + | |
| 300 | + should "ensure #to_hash is called on objects that support it" do | |
| 301 | + assert_nothing_raised do | |
| 302 | + build_notice(:session => { :object => stub(:to_hash => {}) }) | |
| 303 | + end | |
| 304 | + end | |
| 305 | + | |
| 306 | + def assert_accepts_exception_attribute(attribute, args = {}, &block) | |
| 307 | + exception = build_exception | |
| 308 | + block ||= lambda { exception.send(attribute) } | |
| 309 | + value = block.call(exception) | |
| 310 | + | |
| 311 | + notice_from_exception = build_notice(args.merge(:exception => exception)) | |
| 312 | + | |
| 313 | + assert_equal notice_from_exception.send(attribute), | |
| 314 | + value, | |
| 315 | + "#{attribute} was not correctly set from an exception" | |
| 316 | + | |
| 317 | + notice_from_hash = build_notice(args.merge(attribute => value)) | |
| 318 | + assert_equal notice_from_hash.send(attribute), | |
| 319 | + value, | |
| 320 | + "#{attribute} was not correctly set from a hash" | |
| 321 | + end | |
| 322 | + | |
| 323 | + def assert_serializes_hash(attribute) | |
| 324 | + [File.open(__FILE__), Proc.new { puts "boo!" }, Module.new].each do |object| | |
| 325 | + hash = { | |
| 326 | + :strange_object => object, | |
| 327 | + :sub_hash => { | |
| 328 | + :sub_object => object | |
| 329 | + }, | |
| 330 | + :array => [object] | |
| 331 | + } | |
| 332 | + notice = build_notice(attribute => hash) | |
| 333 | + hash = notice.send(attribute) | |
| 334 | + assert_equal object.to_s, hash[:strange_object], "objects should be serialized" | |
| 335 | + assert_kind_of Hash, hash[:sub_hash], "subhashes should be kept" | |
| 336 | + assert_equal object.to_s, hash[:sub_hash][:sub_object], "subhash members should be serialized" | |
| 337 | + assert_kind_of Array, hash[:array], "arrays should be kept" | |
| 338 | + assert_equal object.to_s, hash[:array].first, "array members should be serialized" | |
| 339 | + end | |
| 340 | + end | |
| 341 | + | |
| 342 | + def assert_valid_notice_document(document) | |
| 343 | + xsd_path = File.join(File.dirname(__FILE__), "hoptoad_2_0.xsd") | |
| 344 | + schema = Nokogiri::XML::Schema.new(IO.read(xsd_path)) | |
| 345 | + errors = schema.validate(document) | |
| 346 | + assert errors.empty?, errors.collect{|e| e.message }.join | |
| 347 | + end | |
| 348 | + | |
| 349 | + def assert_filters_hash(attribute) | |
| 350 | + filters = %w(abc def) | |
| 351 | + original = { 'abc' => "123", 'def' => "456", 'ghi' => "789", 'nested' => { 'abc' => '100' } } | |
| 352 | + filtered = { 'abc' => "[FILTERED]", | |
| 353 | + 'def' => "[FILTERED]", | |
| 354 | + 'ghi' => "789", | |
| 355 | + 'nested' => { 'abc' => '[FILTERED]' } } | |
| 356 | + | |
| 357 | + notice = build_notice(:params_filters => filters, attribute => original) | |
| 358 | + | |
| 359 | + assert_equal(filtered, | |
| 360 | + notice.send(attribute)) | |
| 361 | + end | |
| 362 | + | |
| 363 | +end | ... | ... |
vendor/plugins/hoptoad_notifier/test/notifier_test.rb
| 1 | 1 | require File.dirname(__FILE__) + '/helper' |
| 2 | 2 | |
| 3 | -class NotifierTest < ActiveSupport::TestCase | |
| 4 | - context "Sending a notice" do | |
| 5 | - context "with an exception" do | |
| 6 | - setup do | |
| 7 | - @sender = HoptoadNotifier::Sender.new | |
| 8 | - @backtrace = caller | |
| 9 | - @exception = begin | |
| 10 | - raise | |
| 11 | - rescue => caught_exception | |
| 12 | - caught_exception | |
| 13 | - end | |
| 14 | - @options = {:error_message => "123", | |
| 15 | - :backtrace => @backtrace} | |
| 16 | - HoptoadNotifier.instance_variable_set("@backtrace_filters", []) | |
| 17 | - HoptoadNotifier::Sender.expects(:new).returns(@sender) | |
| 18 | - @sender.stubs(:public_environment?).returns(true) | |
| 19 | - end | |
| 3 | +class NotifierTest < Test::Unit::TestCase | |
| 20 | 4 | |
| 21 | - context "when using an HTTP Proxy" do | |
| 22 | - setup do | |
| 23 | - @body = 'body' | |
| 24 | - @response = stub(:body => @body) | |
| 25 | - @http = stub(:post => @response, :read_timeout= => nil, :open_timeout= => nil, :use_ssl= => nil) | |
| 26 | - @sender.stubs(:logger).returns(stub(:error => nil, :info => nil)) | |
| 27 | - @proxy = stub | |
| 28 | - @proxy.stubs(:new).returns(@http) | |
| 29 | - | |
| 30 | - HoptoadNotifier.port = nil | |
| 31 | - HoptoadNotifier.host = nil | |
| 32 | - HoptoadNotifier.secure = false | |
| 33 | - | |
| 34 | - Net::HTTP.expects(:Proxy).with( | |
| 35 | - HoptoadNotifier.proxy_host, | |
| 36 | - HoptoadNotifier.proxy_port, | |
| 37 | - HoptoadNotifier.proxy_user, | |
| 38 | - HoptoadNotifier.proxy_pass | |
| 39 | - ).returns(@proxy) | |
| 40 | - end | |
| 41 | - | |
| 42 | - context "on notify" do | |
| 43 | - setup { HoptoadNotifier.notify(@exception) } | |
| 44 | - | |
| 45 | - before_should "post to Hoptoad" do | |
| 46 | - url = "http://hoptoadapp.com:80/notices/" | |
| 47 | - uri = URI.parse(url) | |
| 48 | - URI.expects(:parse).with(url).returns(uri) | |
| 49 | - @http.expects(:post).with(uri.path, anything, anything).returns(@response) | |
| 50 | - end | |
| 51 | - end | |
| 52 | - end | |
| 5 | + class OriginalException < Exception | |
| 6 | + end | |
| 53 | 7 | |
| 54 | - context "when stubbing out Net::HTTP" do | |
| 55 | - setup do | |
| 56 | - @body = 'body' | |
| 57 | - @response = stub(:body => @body) | |
| 58 | - @http = stub(:post => @response, :read_timeout= => nil, :open_timeout= => nil, :use_ssl= => nil) | |
| 59 | - @sender.stubs(:logger).returns(stub(:error => nil, :info => nil)) | |
| 60 | - Net::HTTP.stubs(:new).returns(@http) | |
| 61 | - HoptoadNotifier.port = nil | |
| 62 | - HoptoadNotifier.host = nil | |
| 63 | - HoptoadNotifier.proxy_host = nil | |
| 64 | - end | |
| 65 | - | |
| 66 | - context "on notify" do | |
| 67 | - setup { HoptoadNotifier.notify(@exception) } | |
| 68 | - | |
| 69 | - before_should "post to the right url for non-ssl" do | |
| 70 | - HoptoadNotifier.secure = false | |
| 71 | - url = "http://hoptoadapp.com:80/notices/" | |
| 72 | - uri = URI.parse(url) | |
| 73 | - URI.expects(:parse).with(url).returns(uri) | |
| 74 | - @http.expects(:post).with(uri.path, anything, anything).returns(@response) | |
| 75 | - end | |
| 76 | - | |
| 77 | - before_should "post to the right path" do | |
| 78 | - @http.expects(:post).with("/notices/", anything, anything).returns(@response) | |
| 79 | - end | |
| 80 | - | |
| 81 | - before_should "call send_to_hoptoad" do | |
| 82 | - @sender.expects(:send_to_hoptoad) | |
| 83 | - end | |
| 84 | - | |
| 85 | - before_should "default the open timeout to 2 seconds" do | |
| 86 | - HoptoadNotifier.http_open_timeout = nil | |
| 87 | - @http.expects(:open_timeout=).with(2) | |
| 88 | - end | |
| 89 | - | |
| 90 | - before_should "default the read timeout to 5 seconds" do | |
| 91 | - HoptoadNotifier.http_read_timeout = nil | |
| 92 | - @http.expects(:read_timeout=).with(5) | |
| 93 | - end | |
| 94 | - | |
| 95 | - before_should "allow override of the open timeout" do | |
| 96 | - HoptoadNotifier.http_open_timeout = 4 | |
| 97 | - @http.expects(:open_timeout=).with(4) | |
| 98 | - end | |
| 99 | - | |
| 100 | - before_should "allow override of the read timeout" do | |
| 101 | - HoptoadNotifier.http_read_timeout = 10 | |
| 102 | - @http.expects(:read_timeout=).with(10) | |
| 103 | - end | |
| 104 | - | |
| 105 | - before_should "connect to the right port for ssl" do | |
| 106 | - HoptoadNotifier.secure = true | |
| 107 | - Net::HTTP.expects(:new).with("hoptoadapp.com", 443).returns(@http) | |
| 108 | - end | |
| 109 | - | |
| 110 | - before_should "connect to the right port for non-ssl" do | |
| 111 | - HoptoadNotifier.secure = false | |
| 112 | - Net::HTTP.expects(:new).with("hoptoadapp.com", 80).returns(@http) | |
| 113 | - end | |
| 114 | - | |
| 115 | - before_should "use ssl if secure" do | |
| 116 | - HoptoadNotifier.secure = true | |
| 117 | - HoptoadNotifier.host = 'example.org' | |
| 118 | - Net::HTTP.expects(:new).with('example.org', 443).returns(@http) | |
| 119 | - end | |
| 120 | - | |
| 121 | - before_should "not use ssl if not secure" do | |
| 122 | - HoptoadNotifier.secure = nil | |
| 123 | - HoptoadNotifier.host = 'example.org' | |
| 124 | - Net::HTTP.expects(:new).with('example.org', 80).returns(@http) | |
| 125 | - end | |
| 126 | - end | |
| 127 | - end | |
| 8 | + class ContinuedException < Exception | |
| 9 | + end | |
| 128 | 10 | |
| 129 | - should "send as if it were a normally caught exception" do | |
| 130 | - @sender.expects(:notify_hoptoad).with(@exception) | |
| 131 | - HoptoadNotifier.notify(@exception) | |
| 132 | - end | |
| 11 | + include DefinesConstants | |
| 12 | + | |
| 13 | + def setup | |
| 14 | + super | |
| 15 | + reset_config | |
| 16 | + end | |
| 17 | + | |
| 18 | + def assert_sent(notice, notice_args) | |
| 19 | + assert_received(HoptoadNotifier::Notice, :new) {|expect| expect.with(has_entries(notice_args)) } | |
| 20 | + assert_received(notice, :to_xml) | |
| 21 | + assert_received(HoptoadNotifier.sender, :send_to_hoptoad) {|expect| expect.with(notice.to_xml) } | |
| 22 | + end | |
| 23 | + | |
| 24 | + def set_public_env | |
| 25 | + HoptoadNotifier.configure { |config| config.environment_name = 'production' } | |
| 26 | + end | |
| 27 | + | |
| 28 | + def set_development_env | |
| 29 | + HoptoadNotifier.configure { |config| config.environment_name = 'development' } | |
| 30 | + end | |
| 31 | + | |
| 32 | + should "yield and save a configuration when configuring" do | |
| 33 | + yielded_configuration = nil | |
| 34 | + HoptoadNotifier.configure do |config| | |
| 35 | + yielded_configuration = config | |
| 36 | + end | |
| 37 | + | |
| 38 | + assert_kind_of HoptoadNotifier::Configuration, yielded_configuration | |
| 39 | + assert_equal yielded_configuration, HoptoadNotifier.configuration | |
| 40 | + end | |
| 41 | + | |
| 42 | + should "not remove existing config options when configuring twice" do | |
| 43 | + first_config = nil | |
| 44 | + HoptoadNotifier.configure do |config| | |
| 45 | + first_config = config | |
| 46 | + end | |
| 47 | + HoptoadNotifier.configure do |config| | |
| 48 | + assert_equal first_config, config | |
| 49 | + end | |
| 50 | + end | |
| 51 | + | |
| 52 | + should "configure the sender" do | |
| 53 | + sender = stub_sender | |
| 54 | + HoptoadNotifier::Sender.stubs(:new => sender) | |
| 55 | + configuration = nil | |
| 56 | + | |
| 57 | + HoptoadNotifier.configure { |yielded_config| configuration = yielded_config } | |
| 58 | + | |
| 59 | + assert_received(HoptoadNotifier::Sender, :new) { |expect| expect.with(configuration) } | |
| 60 | + assert_equal sender, HoptoadNotifier.sender | |
| 61 | + end | |
| 62 | + | |
| 63 | + should "create and send a notice for an exception" do | |
| 64 | + set_public_env | |
| 65 | + exception = build_exception | |
| 66 | + stub_sender! | |
| 67 | + notice = stub_notice! | |
| 68 | + | |
| 69 | + HoptoadNotifier.notify(exception) | |
| 70 | + | |
| 71 | + assert_sent notice, :exception => exception | |
| 72 | + end | |
| 73 | + | |
| 74 | + should "create and send a notice for a hash" do | |
| 75 | + set_public_env | |
| 76 | + notice = stub_notice! | |
| 77 | + notice_args = { :error_message => 'uh oh' } | |
| 78 | + stub_sender! | |
| 79 | + | |
| 80 | + HoptoadNotifier.notify(notice_args) | |
| 81 | + | |
| 82 | + assert_sent(notice, notice_args) | |
| 83 | + end | |
| 84 | + | |
| 85 | + should "create and sent a notice for an exception and hash" do | |
| 86 | + set_public_env | |
| 87 | + exception = build_exception | |
| 88 | + notice = stub_notice! | |
| 89 | + notice_args = { :error_message => 'uh oh' } | |
| 90 | + stub_sender! | |
| 91 | + | |
| 92 | + HoptoadNotifier.notify(exception, notice_args) | |
| 93 | + | |
| 94 | + assert_sent(notice, notice_args.merge(:exception => exception)) | |
| 95 | + end | |
| 133 | 96 | |
| 134 | - should "make sure the exception is munged into a hash" do | |
| 135 | - options = HoptoadNotifier.default_notice_options.merge({ | |
| 136 | - :backtrace => @exception.backtrace, | |
| 137 | - :environment => ENV.to_hash, | |
| 138 | - :error_class => @exception.class.name, | |
| 139 | - :error_message => "#{@exception.class.name}: #{@exception.message}", | |
| 140 | - :api_key => HoptoadNotifier.api_key, | |
| 141 | - }) | |
| 142 | - @sender.expects(:send_to_hoptoad).with(:notice => options) | |
| 143 | - HoptoadNotifier.notify(@exception) | |
| 97 | + should "not create a notice in a development environment" do | |
| 98 | + set_development_env | |
| 99 | + sender = stub_sender! | |
| 100 | + | |
| 101 | + HoptoadNotifier.notify(build_exception) | |
| 102 | + HoptoadNotifier.notify_or_ignore(build_exception) | |
| 103 | + | |
| 104 | + assert_received(sender, :send_to_hoptoad) {|expect| expect.never } | |
| 105 | + end | |
| 106 | + | |
| 107 | + should "not deliver an ignored exception when notifying implicitly" do | |
| 108 | + set_public_env | |
| 109 | + exception = build_exception | |
| 110 | + sender = stub_sender! | |
| 111 | + notice = stub_notice! | |
| 112 | + notice.stubs(:ignore? => true) | |
| 113 | + | |
| 114 | + HoptoadNotifier.notify_or_ignore(exception) | |
| 115 | + | |
| 116 | + assert_received(sender, :send_to_hoptoad) {|expect| expect.never } | |
| 117 | + end | |
| 118 | + | |
| 119 | + should "deliver an ignored exception when notifying manually" do | |
| 120 | + set_public_env | |
| 121 | + exception = build_exception | |
| 122 | + sender = stub_sender! | |
| 123 | + notice = stub_notice! | |
| 124 | + notice.stubs(:ignore? => true) | |
| 125 | + | |
| 126 | + HoptoadNotifier.notify(exception) | |
| 127 | + | |
| 128 | + assert_sent(notice, :exception => exception) | |
| 129 | + end | |
| 130 | + | |
| 131 | + should "pass config to created notices" do | |
| 132 | + exception = build_exception | |
| 133 | + config_opts = { 'one' => 'two', 'three' => 'four' } | |
| 134 | + notice = stub_notice! | |
| 135 | + stub_sender! | |
| 136 | + HoptoadNotifier.configuration = stub('config', :merge => config_opts, :public? => true) | |
| 137 | + | |
| 138 | + HoptoadNotifier.notify(exception) | |
| 139 | + | |
| 140 | + assert_received(HoptoadNotifier::Notice, :new) do |expect| | |
| 141 | + expect.with(has_entries(config_opts)) | |
| 142 | + end | |
| 143 | + end | |
| 144 | + | |
| 145 | + context "building notice JSON for an exception" do | |
| 146 | + setup do | |
| 147 | + @params = { :controller => "users", :action => "create" } | |
| 148 | + @exception = build_exception | |
| 149 | + @hash = HoptoadNotifier.build_lookup_hash_for(@exception, @params) | |
| 150 | + end | |
| 151 | + | |
| 152 | + should "set action" do | |
| 153 | + assert_equal @params[:action], @hash[:action] | |
| 154 | + end | |
| 155 | + | |
| 156 | + should "set controller" do | |
| 157 | + assert_equal @params[:controller], @hash[:component] | |
| 158 | + end | |
| 159 | + | |
| 160 | + should "set line number" do | |
| 161 | + assert @hash[:line_number] =~ /\d+/ | |
| 162 | + end | |
| 163 | + | |
| 164 | + should "set file" do | |
| 165 | + assert_match /\/test\/helper\.rb$/, @hash[:file] | |
| 166 | + end | |
| 167 | + | |
| 168 | + should "set rails_env to production" do | |
| 169 | + assert_equal 'production', @hash[:environment_name] | |
| 170 | + end | |
| 171 | + | |
| 172 | + should "set error class" do | |
| 173 | + assert_equal 'RuntimeError', @hash[:error_class] | |
| 174 | + end | |
| 175 | + | |
| 176 | + should "not set file or line number with no backtrace" do | |
| 177 | + @exception.stubs(:backtrace).returns([]) | |
| 178 | + | |
| 179 | + @hash = HoptoadNotifier.build_lookup_hash_for(@exception) | |
| 180 | + | |
| 181 | + assert_nil @hash[:line_number] | |
| 182 | + assert_nil @hash[:file] | |
| 183 | + end | |
| 184 | + | |
| 185 | + should "not set action or controller when not provided" do | |
| 186 | + @hash = HoptoadNotifier.build_lookup_hash_for(@exception) | |
| 187 | + | |
| 188 | + assert_nil @hash[:action] | |
| 189 | + assert_nil @hash[:controller] | |
| 190 | + end | |
| 191 | + | |
| 192 | + context "when an exception that provides #original_exception is raised" do | |
| 193 | + setup do | |
| 194 | + @exception.stubs(:original_exception).returns(begin | |
| 195 | + raise NotifierTest::OriginalException.new | |
| 196 | + rescue Exception => e | |
| 197 | + e | |
| 198 | + end) | |
| 144 | 199 | end |
| 145 | 200 | |
| 146 | - should "parse massive one-line exceptions into multiple lines" do | |
| 147 | - @original_backtrace = "one big line\n separated\n by new lines\nand some spaces" | |
| 148 | - @expected_backtrace = ["one big line", "separated", "by new lines", "and some spaces"] | |
| 149 | - @exception.set_backtrace [@original_backtrace] | |
| 150 | - | |
| 151 | - options = HoptoadNotifier.default_notice_options.merge({ | |
| 152 | - :backtrace => @expected_backtrace, | |
| 153 | - :environment => ENV.to_hash, | |
| 154 | - :error_class => @exception.class.name, | |
| 155 | - :error_message => "#{@exception.class.name}: #{@exception.message}", | |
| 156 | - :api_key => HoptoadNotifier.api_key, | |
| 157 | - }) | |
| 158 | - | |
| 159 | - @sender.expects(:send_to_hoptoad).with(:notice => options) | |
| 160 | - HoptoadNotifier.notify(@exception) | |
| 201 | + should "unwrap exceptions that provide #original_exception" do | |
| 202 | + @hash = HoptoadNotifier.build_lookup_hash_for(@exception) | |
| 203 | + assert_equal "NotifierTest::OriginalException", @hash[:error_class] | |
| 161 | 204 | end |
| 162 | 205 | end |
| 163 | 206 | |
| 164 | - context "without an exception" do | |
| 207 | + context "when an exception that provides #continued_exception is raised" do | |
| 165 | 208 | setup do |
| 166 | - @sender = HoptoadNotifier::Sender.new | |
| 167 | - @backtrace = caller | |
| 168 | - @options = {:error_message => "123", | |
| 169 | - :backtrace => @backtrace} | |
| 170 | - HoptoadNotifier::Sender.expects(:new).returns(@sender) | |
| 209 | + @exception.stubs(:continued_exception).returns(begin | |
| 210 | + raise NotifierTest::ContinuedException.new | |
| 211 | + rescue Exception => e | |
| 212 | + e | |
| 213 | + end) | |
| 171 | 214 | end |
| 172 | 215 | |
| 173 | - should "send sensible defaults" do | |
| 174 | - @sender.expects(:notify_hoptoad).with(@options) | |
| 175 | - HoptoadNotifier.notify(:error_message => "123", :backtrace => @backtrace) | |
| 216 | + should "unwrap exceptions that provide #continued_exception" do | |
| 217 | + @hash = HoptoadNotifier.build_lookup_hash_for(@exception) | |
| 218 | + assert_equal "NotifierTest::ContinuedException", @hash[:error_class] | |
| 176 | 219 | end |
| 177 | 220 | end |
| 178 | 221 | end | ... | ... |
| ... | ... | @@ -0,0 +1,123 @@ |
| 1 | +require File.dirname(__FILE__) + '/helper' | |
| 2 | + | |
| 3 | +class SenderTest < Test::Unit::TestCase | |
| 4 | + | |
| 5 | + def setup | |
| 6 | + reset_config | |
| 7 | + end | |
| 8 | + | |
| 9 | + def build_sender(opts = {}) | |
| 10 | + config = HoptoadNotifier::Configuration.new | |
| 11 | + opts.each {|opt, value| config.send(:"#{opt}=", value) } | |
| 12 | + HoptoadNotifier::Sender.new(config) | |
| 13 | + end | |
| 14 | + | |
| 15 | + def send_exception(args = {}) | |
| 16 | + notice = args.delete(:notice) || build_notice_data | |
| 17 | + sender = args.delete(:sender) || build_sender(args) | |
| 18 | + sender.send_to_hoptoad(notice) | |
| 19 | + sender | |
| 20 | + end | |
| 21 | + | |
| 22 | + def stub_http | |
| 23 | + response = stub(:body => 'body') | |
| 24 | + http = stub(:post => response, | |
| 25 | + :read_timeout= => nil, | |
| 26 | + :open_timeout= => nil, | |
| 27 | + :use_ssl= => nil) | |
| 28 | + Net::HTTP.stubs(:new => http) | |
| 29 | + http | |
| 30 | + end | |
| 31 | + | |
| 32 | + should "post to Hoptoad when using an HTTP proxy" do | |
| 33 | + response = stub(:body => 'body') | |
| 34 | + http = stub(:post => response, | |
| 35 | + :read_timeout= => nil, | |
| 36 | + :open_timeout= => nil, | |
| 37 | + :use_ssl= => nil) | |
| 38 | + proxy = stub(:new => http) | |
| 39 | + Net::HTTP.stubs(:Proxy => proxy) | |
| 40 | + | |
| 41 | + url = "http://hoptoadapp.com:80#{HoptoadNotifier::Sender::NOTICES_URI}" | |
| 42 | + uri = URI.parse(url) | |
| 43 | + | |
| 44 | + proxy_host = 'some.host' | |
| 45 | + proxy_port = 88 | |
| 46 | + proxy_user = 'login' | |
| 47 | + proxy_pass = 'passwd' | |
| 48 | + | |
| 49 | + send_exception(:proxy_host => proxy_host, | |
| 50 | + :proxy_port => proxy_port, | |
| 51 | + :proxy_user => proxy_user, | |
| 52 | + :proxy_pass => proxy_pass) | |
| 53 | + assert_received(http, :post) do |expect| | |
| 54 | + expect.with(uri.path, anything, HoptoadNotifier::HEADERS) | |
| 55 | + end | |
| 56 | + assert_received(Net::HTTP, :Proxy) do |expect| | |
| 57 | + expect.with(proxy_host, proxy_port, proxy_user, proxy_pass) | |
| 58 | + end | |
| 59 | + end | |
| 60 | + | |
| 61 | + should "post to the right url for non-ssl" do | |
| 62 | + http = stub_http | |
| 63 | + url = "http://hoptoadapp.com:80#{HoptoadNotifier::Sender::NOTICES_URI}" | |
| 64 | + uri = URI.parse(url) | |
| 65 | + send_exception(:secure => false) | |
| 66 | + assert_received(http, :post) {|expect| expect.with(uri.path, anything, HoptoadNotifier::HEADERS) } | |
| 67 | + end | |
| 68 | + | |
| 69 | + should "post to the right path for ssl" do | |
| 70 | + http = stub_http | |
| 71 | + send_exception(:secure => true) | |
| 72 | + assert_received(http, :post) {|expect| expect.with(HoptoadNotifier::Sender::NOTICES_URI, anything, HoptoadNotifier::HEADERS) } | |
| 73 | + end | |
| 74 | + | |
| 75 | + should "default the open timeout to 2 seconds" do | |
| 76 | + http = stub_http | |
| 77 | + send_exception | |
| 78 | + assert_received(http, :open_timeout=) {|expect| expect.with(2) } | |
| 79 | + end | |
| 80 | + | |
| 81 | + should "default the read timeout to 5 seconds" do | |
| 82 | + http = stub_http | |
| 83 | + send_exception | |
| 84 | + assert_received(http, :read_timeout=) {|expect| expect.with(5) } | |
| 85 | + end | |
| 86 | + | |
| 87 | + should "allow override of the open timeout" do | |
| 88 | + http = stub_http | |
| 89 | + send_exception(:http_open_timeout => 4) | |
| 90 | + assert_received(http, :open_timeout=) {|expect| expect.with(4) } | |
| 91 | + end | |
| 92 | + | |
| 93 | + should "allow override of the read timeout" do | |
| 94 | + http = stub_http | |
| 95 | + send_exception(:http_read_timeout => 10) | |
| 96 | + assert_received(http, :read_timeout=) {|expect| expect.with(10) } | |
| 97 | + end | |
| 98 | + | |
| 99 | + should "connect to the right port for ssl" do | |
| 100 | + stub_http | |
| 101 | + send_exception(:secure => true) | |
| 102 | + assert_received(Net::HTTP, :new) {|expect| expect.with("hoptoadapp.com", 443) } | |
| 103 | + end | |
| 104 | + | |
| 105 | + should "connect to the right port for non-ssl" do | |
| 106 | + stub_http | |
| 107 | + send_exception(:secure => false) | |
| 108 | + assert_received(Net::HTTP, :new) {|expect| expect.with("hoptoadapp.com", 80) } | |
| 109 | + end | |
| 110 | + | |
| 111 | + should "use ssl if secure" do | |
| 112 | + stub_http | |
| 113 | + send_exception(:secure => true, :host => 'example.org') | |
| 114 | + assert_received(Net::HTTP, :new) {|expect| expect.with('example.org', 443) } | |
| 115 | + end | |
| 116 | + | |
| 117 | + should "not use ssl if not secure" do | |
| 118 | + stub_http | |
| 119 | + send_exception(:secure => false, :host => 'example.org') | |
| 120 | + assert_received(Net::HTTP, :new) {|expect| expect.with('example.org', 80) } | |
| 121 | + end | |
| 122 | + | |
| 123 | +end | ... | ... |