From 23a288e7002416df02b2aaa07916caf0c3614dd5 Mon Sep 17 00:00:00 2001 From: Pius Uzamere Date: Wed, 6 Jan 2010 14:16:16 -0500 Subject: [PATCH] updated hoptoad --- vendor/plugins/hoptoad_notifier/.yardopts | 3 +++ vendor/plugins/hoptoad_notifier/INSTALL | 50 ++++++++++---------------------------------------- vendor/plugins/hoptoad_notifier/README | 185 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- vendor/plugins/hoptoad_notifier/README.rdoc | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/plugins/hoptoad_notifier/Rakefile | 17 ++++++++--------- vendor/plugins/hoptoad_notifier/TESTING.rdoc | 8 ++++++++ vendor/plugins/hoptoad_notifier/ginger_scenarios.rb | 10 ++++++++++ vendor/plugins/hoptoad_notifier/hoptoad_notifier.gemspec | 21 --------------------- vendor/plugins/hoptoad_notifier/init.rb | 1 + vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier.rb | 412 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/backtrace.rb | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/catcher.rb | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/configuration.rb | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/notice.rb | 295 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/sender.rb | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/plugins/hoptoad_notifier/lib/hoptoad_tasks.rb | 17 ++++++++++++++--- vendor/plugins/hoptoad_notifier/lib/templates/rescue.erb | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/plugins/hoptoad_notifier/rails/init.rb | 8 ++++++++ vendor/plugins/hoptoad_notifier/recipes/hoptoad.rb | 11 +++++++---- vendor/plugins/hoptoad_notifier/script/integration_test.rb | 20 +++++++++++++++----- vendor/plugins/hoptoad_notifier/tasks/hoptoad_notifier_tasks.rake | 39 +++++++++++++++++++++++++++++++-------- vendor/plugins/hoptoad_notifier/test/backtrace_test.rb | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/plugins/hoptoad_notifier/test/catcher_test.rb | 314 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/plugins/hoptoad_notifier/test/configuration_test.rb | 290 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------------------------------------------------------------------------------- vendor/plugins/hoptoad_notifier/test/controller_test.rb | 366 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ vendor/plugins/hoptoad_notifier/test/helper.rb | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ vendor/plugins/hoptoad_notifier/test/hoptoad_2_0.xsd | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/plugins/hoptoad_notifier/test/hoptoad_tasks_test.rb | 11 +++++++++-- vendor/plugins/hoptoad_notifier/test/logger_test.rb | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/plugins/hoptoad_notifier/test/notice_test.rb | 363 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/plugins/hoptoad_notifier/test/notifier_test.rb | 365 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------------------------------------------------------------------------------------------------------------------- vendor/plugins/hoptoad_notifier/test/sender_test.rb | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 32 files changed, 2936 insertions(+), 1248 deletions(-) create mode 100644 vendor/plugins/hoptoad_notifier/.yardopts delete mode 100644 vendor/plugins/hoptoad_notifier/README create mode 100644 vendor/plugins/hoptoad_notifier/README.rdoc create mode 100644 vendor/plugins/hoptoad_notifier/TESTING.rdoc delete mode 100644 vendor/plugins/hoptoad_notifier/hoptoad_notifier.gemspec create mode 100644 vendor/plugins/hoptoad_notifier/init.rb create mode 100644 vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/backtrace.rb create mode 100644 vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/catcher.rb create mode 100644 vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/configuration.rb create mode 100644 vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/notice.rb create mode 100644 vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/sender.rb create mode 100644 vendor/plugins/hoptoad_notifier/lib/templates/rescue.erb create mode 100644 vendor/plugins/hoptoad_notifier/rails/init.rb create mode 100644 vendor/plugins/hoptoad_notifier/test/backtrace_test.rb create mode 100644 vendor/plugins/hoptoad_notifier/test/catcher_test.rb delete mode 100644 vendor/plugins/hoptoad_notifier/test/controller_test.rb create mode 100644 vendor/plugins/hoptoad_notifier/test/hoptoad_2_0.xsd create mode 100644 vendor/plugins/hoptoad_notifier/test/logger_test.rb create mode 100644 vendor/plugins/hoptoad_notifier/test/notice_test.rb create mode 100644 vendor/plugins/hoptoad_notifier/test/sender_test.rb diff --git a/vendor/plugins/hoptoad_notifier/.yardopts b/vendor/plugins/hoptoad_notifier/.yardopts new file mode 100644 index 0000000..485046d --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/.yardopts @@ -0,0 +1,3 @@ +- +TESTING.rdoc +MIT-LICENSE diff --git a/vendor/plugins/hoptoad_notifier/INSTALL b/vendor/plugins/hoptoad_notifier/INSTALL index da3514c..39f7fb2 100644 --- a/vendor/plugins/hoptoad_notifier/INSTALL +++ b/vendor/plugins/hoptoad_notifier/INSTALL @@ -1,55 +1,25 @@ -HoptoadNotifier -=============== - -This is the notifier plugin for integrating apps with Hoptoad. - -When an uncaught exception occurs, HoptoadNotifier will POST the relevant data -to the Hoptoad server specified in your environment. - - -INSTALLATION ------------- - -REMOVE EXCEPTION_NOTIFIER - -In your ApplicationController, REMOVE this line: - - include ExceptionNotifiable - -In your config/environment* files, remove all references to ExceptionNotifier - -Remove the vendor/plugins/exception_notifier directory. - -INSTALL HOPTOAD_NOTIFIER - -From your project's RAILS_ROOT, run: - - script/plugin install git://github.com/thoughtbot/hoptoad_notifier.git - -CONFIGURATION +=== Configuration You should have something like this in config/initializers/hoptoad.rb. HoptoadNotifier.configure do |config| config.api_key = '1234567890abcdef' end - + (Please note that this configuration should be in a global configuration, and -is *not* enrivonment-specific. Hoptoad is smart enough to know what errors are +is *not* environment-specific. Hoptoad is smart enough to know what errors are caused by what environments, so your staging errors don't get mixed in with your production errors.) -After this is in place, all exceptions will be logged to Hoptoad where they can -be aggregated, filtered, sorted, analyzed, massaged, and searched. - -** NOTE FOR RAILS 1.2.* USERS: ** -You will need to copy the hoptoad_notifier_tasks.rake file into your -RAILS_ROOT/lib/tasks directory in order for the following to work: - -You can test that hoptoad is working in your production environment by using +You can test that Hoptoad is working in your production environment by using this rake task (from RAILS_ROOT): rake hoptoad:test -If everything is configured properly, that task will send a notice to hoptoad +If everything is configured properly, that task will send a notice to Hoptoad which will be visible immediately. + +NOTE FOR RAILS 1.2.* USERS: + +You will need to copy the hoptoad_notifier_tasks.rake file into your +RAILS_ROOT/lib/tasks directory in order for the rake hoptoad:test task to work. diff --git a/vendor/plugins/hoptoad_notifier/README b/vendor/plugins/hoptoad_notifier/README deleted file mode 100644 index 0156bef..0000000 --- a/vendor/plugins/hoptoad_notifier/README +++ /dev/null @@ -1,185 +0,0 @@ -HoptoadNotifier -=============== - -This is the notifier plugin for integrating apps with Hoptoad. - -When an uncaught exception occurs, HoptoadNotifier will POST the relevant data -to the Hoptoad server specified in your environment. - -INSTALLATION ------------- - -REMOVE EXCEPTION_NOTIFIER - -In your ApplicationController, REMOVE this line: - - include ExceptionNotifiable - -In your config/environment* files, remove all references to ExceptionNotifier - -Remove the vendor/plugins/exception_notifier directory. - -INSTALL HOPTOAD_NOTIFIER - -From your project's RAILS_ROOT, run: - - script/plugin install git://github.com/thoughtbot/hoptoad_notifier.git - -CONFIGURATION - -You should have something like this in config/initializers/hoptoad.rb. - - HoptoadNotifier.configure do |config| - config.api_key = '1234567890abcdef' - end - -(Please note that this configuration should be in a global configuration, and -is *not* environment-specific. Hoptoad is smart enough to know what errors are -caused by what environments, so your staging errors don't get mixed in with -your production errors.) - -That should be it! Now all exceptions will be logged to Hoptoad where they can -be aggregated, filtered, sorted, analyzed, massaged, and searched. In previous -releases you had to include HoptoadNotifier::Catcher into your -ApplicationController, but the plugin takes care of that now. - -** NOTE FOR RAILS 1.2.* USERS: ** -You will need to copy the hoptoad_notifier_tasks.rake file into your -RAILS_ROOT/lib/tasks directory in order for the following to work: - -You can test that hoptoad is working in your production environment by using -this rake task (from RAILS_ROOT): - - rake hoptoad:test - -If everything is configured properly, that task will send a notice to hoptoad -which will be visible immediately. - -USAGE - -For the most part, hoptoad works for itself. Once you've included the notifier -in your ApplicationController (which is now done automatically by the plugin), -all errors will be rescued by the #rescue_action_in_public provided by the plugin. - -If you want to log arbitrary things which you've rescued yourself from a -controller, you can do something like this: - - ... - rescue => ex - notify_hoptoad(ex) - flash[:failure] = 'Encryptions could not be rerouted, try again.' - end - ... - -The #notify_hoptoad call will send the notice over to hoptoad for later -analysis. - -TRACKING DEPLOYMENTS IN HOPTOAD - -Paying Hoptoad plans support the ability to track deployments of your application in Hoptoad. -By notify Hoptoad of your application deployments, all errors are resolved when a deploy occurs, -so that you'll be notified again about any errors that reoccur after a deployment. - -Additionally, it's possible to review the errors in Hoptoad that occurred before and after a deploy. - -When Hoptoad is installed as a plugin this functionality is loaded automatically (if you have Capistrano version 2.0.0 or greater). - -When Hoptoad installed as a gem, you need to add - require 'hoptoad_notifier/recipes/hoptoad' -to your deploy.rb - -GOING BEYOND EXCEPTIONS - -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: - - begin - params = { - # params that you pass to a method that can throw an exception - } - my_unpredicable_method(params) - rescue => e - HoptoadNotifier.notify( - :error_class => "Special Error", - :error_message => "Special Error: #{e.message}", - :request => { :params => params } - ) - end - -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: - - * :error_class – Use this to group similar errors together. When Hoptoad catches an exception it sends the class name of that exception object. - * :error_message – This is the title of the error you see in the errors list. For exceptions it is "#{exception.class.name}: #{exception.message}" - * :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. - -Hoptoad merges the hash you pass with these default options: - - def default_notice_options - { - :api_key => HoptoadNotifier.api_key, - :error_message => 'Notification', - :backtrace => caller, - :request => {}, - :session => {}, - :environment => ENV.to_hash - } - end - -You can override any of those parameters. - -FILTERING - -You can specify a whitelist of errors, that Hoptoad will not report on. Use -this feature when you are so apathetic to certain errors that you don't want -them even logged. - -This filter will only be applied to automatic notifications, not manual -notifications (when #notify is called directly). - -Hoptoad ignores the following exceptions by default: - ActiveRecord::RecordNotFound - ActionController::RoutingError - ActionController::InvalidAuthenticityToken - CGI::Session::CookieStore::TamperedWithCookie - -To ignore errors in addition to those, specify their names in your Hoptoad -configuration block. - - HoptoadNotifier.configure do |config| - config.api_key = '1234567890abcdef' - config.ignore << ActiveRecord::IgnoreThisError - end - -To ignore *only* certain errors (and override the defaults), use the -#ignore_only attribute. - - HoptoadNotifier.configure do |config| - config.api_key = '1234567890abcdef' - config.ignore_only = [ActiveRecord::IgnoreThisError] - end - -To ignore certain user agents, add in the #ignore_user_agent attribute as a -string or regexp: - - HoptoadNotifier.configure do |config| - config.api_key = '1234567890abcdef' - config.ignore_user_agent << /Ignored/ - config.ignore_user_agent << 'IgnoredUserAgent' - end - -TESTING - -When you run your tests, you might notice that the hoptoad service is recording -notices generated using #notify when you don't expect it to. You can -use code like this in your test_helper.rb to redefine that method so those -errors are not reported while running tests. - - module HoptoadNotifier::Catcher - def notify(thing) - # do nothing. - end - end - -THANKS - -Thanks to Eugene Bolshakov for the excellent write-up on GOING BEYOND EXCEPTIONS, which we have included above. - diff --git a/vendor/plugins/hoptoad_notifier/README.rdoc b/vendor/plugins/hoptoad_notifier/README.rdoc new file mode 100644 index 0000000..9dfdf8d --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/README.rdoc @@ -0,0 +1,227 @@ += HoptoadNotifier + +This is the notifier plugin for integrating apps with Hoptoad. + +When an uncaught exception occurs, HoptoadNotifier will POST the relevant data +to the Hoptoad server specified in your environment. + +== Help + +* {IRC}[irc://irc.freenode.net/thoughtbot] +* {mailing list}[http://groups.google.com/group/hoptoad-notifier-dev] + +== Installation + +=== Remove exception_notifier + +in your ApplicationController, REMOVE this line: + + include ExceptionNotifiable + +In your config/environment* files, remove all references to ExceptionNotifier + +Remove the vendor/plugins/exception_notifier directory. + +=== Install hoptoad_notifier + +from your project's RAILS_ROOT, run: + + script/plugin install -f git://github.com/thoughtbot/hoptoad_notifier.git + +=== Configuration + +You should have something like this in config/initializers/hoptoad.rb. + + HoptoadNotifier.configure do |config| + config.api_key = '1234567890abcdef' + end + +(Please note that this configuration should be in a global configuration, and +is *not* environment-specific. Hoptoad is smart enough to know what errors are +caused by what environments, so your staging errors don't get mixed in with +your production errors.) + +After adding to your config/initializers like this you must restart your +server. This will not affect the rake task but it bears stating. + +That should be it! Now all exceptions will be logged to Hoptoad where they can +be aggregated, filtered, sorted, analyzed, massaged, and searched. In previous +releases you had to include HoptoadNotifier::Catcher into your +ApplicationController, but the plugin takes care of that now. + +You can test that Hoptoad is working in your production environment by using +this rake task (from RAILS_ROOT): + + rake hoptoad:test + +If everything is configured properly, that task will send a notice to Hoptoad +which will be visible immediately. + +=== NOTE FOR RAILS 1.2.* USERS: + +You will need to copy the hoptoad_notifier_tasks.rake file into your +RAILS_ROOT/lib/tasks directory in order for the rake hoptoad:test task to work. + +== Usage + +for the most part, Hoptoad works for itself. Once you've included the notifier +in your ApplicationController (which is now done automatically by the plugin), +all errors will be rescued by the #rescue_action_in_public provided by the plugin. + +If you want to log arbitrary things which you've rescued yourself from a +controller, you can do something like this: + + ... + rescue => ex + notify_hoptoad(ex) + flash[:failure] = 'Encryptions could not be rerouted, try again.' + end + ... + +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. + +To perform custom error processing after Hoptoad has been notified, define the instance method #rescue_action_in_public_without_hoptoad(exception) in your controller. + +== Tracking deployments in Hoptoad + +Paying Hoptoad plans support the ability to track deployments of your application in Hoptoad. +By notifying Hoptoad of your application deployments, all errors are resolved when a deploy occurs, +so that you'll be notified again about any errors that reoccur after a deployment. + +Additionally, it's possible to review the errors in Hoptoad that occurred before and after a deploy. + +When Hoptoad is installed as a plugin this functionality is loaded automatically (if you have Capistrano version 2.0.0 or greater). + +When Hoptoad is installed as a gem, you need to add + + require 'hoptoad_notifier/recipes/hoptoad' + +to your deploy.rb + +== Going beyond exceptions + +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: + + begin + params = { + # params that you pass to a method that can throw an exception + } + my_unpredicable_method(params) + rescue => e + HoptoadNotifier.notify( + :error_class => "Special Error", + :error_message => "Special Error: #{e.message}", + :parameters => params + ) + end + +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: + +* :error_class – Use this to group similar errors together. When Hoptoad catches an exception it sends the class name of that exception object. +* :error_message – This is the title of the error you see in the errors list. For exceptions it is "#{exception.class.name}: #{exception.message}" +* :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. + +Hoptoad merges the hash you pass with these default options: + + { + :api_key => HoptoadNotifier.api_key, + :error_message => 'Notification', + :backtrace => caller, + :parameters => {}, + :session => {} + } + +You can override any of those parameters. + +== Filtering + +You can specify a whitelist of errors, that Hoptoad will not report on. Use +this feature when you are so apathetic to certain errors that you don't want +them even logged. + +This filter will only be applied to automatic notifications, not manual +notifications (when #notify is called directly). + +Hoptoad ignores the following exceptions by default: + + ActiveRecord::RecordNotFound + ActionController::RoutingError + ActionController::InvalidAuthenticityToken + ActionController::UnknownAction + CGI::Session::CookieStore::TamperedWithCookie + +To ignore errors in addition to those, specify their names in your Hoptoad +configuration block. + + HoptoadNotifier.configure do |config| + config.api_key = '1234567890abcdef' + config.ignore << ActiveRecord::IgnoreThisError + end + +To ignore *only* certain errors (and override the defaults), use the +#ignore_only attribute. + + HoptoadNotifier.configure do |config| + config.api_key = '1234567890abcdef' + config.ignore_only = [ActiveRecord::IgnoreThisError] + end + +To ignore certain user agents, add in the #ignore_user_agent attribute as a +string or regexp: + + HoptoadNotifier.configure do |config| + config.api_key = '1234567890abcdef' + config.ignore_user_agent << /Ignored/ + config.ignore_user_agent << 'IgnoredUserAgent' + end + +To ignore exceptions based on other conditions, use #ignore_by_filter: + + HoptoadNotifier.configure do |config| + config.api_key = '1234567890abcdef' + config.ignore_by_filter do |exception_data| + true if exception_data[:error_class] == "RuntimeError" + end + end + +To replace sensitive information sent to the Hoptoad service with [FILTERED] use #params_filters: + + HoptoadNotifier.configure do |config| + config.api_key = '1234567890abcdef' + config.params_filters << "credit_card_number" + end + +Note that, when rescuing exceptions within an ActionController method, +hoptoad_notifier will reuse filters specified by #filter_params_logging. + +== Testing + +When you run your tests, you might notice that the Hoptoad service is recording +notices generated using #notify when you don't expect it to. You can +use code like this in your test_helper.rb to redefine that method so those +errors are not reported while running tests. + + module HoptoadNotifier::Catcher + def notify(thing) + # do nothing. + end + end + +== Supported rails versions + +the notifier currently supports the following versions of Rails: + +* 1.2.6 +* 2.0.2 +* 2.1.0 +* 2.1.2 +* 2.2.2 +* 2.3.2 +* 2.3.3 +* 2.3.4 + +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. + +== Thanks + +Thanks to Eugene Bolshakov for the excellent write-up on GOING BEYOND EXCEPTIONS, which we have included above. diff --git a/vendor/plugins/hoptoad_notifier/Rakefile b/vendor/plugins/hoptoad_notifier/Rakefile index 6e78bc3..42962d7 100644 --- a/vendor/plugins/hoptoad_notifier/Rakefile +++ b/vendor/plugins/hoptoad_notifier/Rakefile @@ -12,15 +12,6 @@ Rake::TestTask.new(:test) do |t| t.verbose = true end -desc 'Generate documentation for the hoptoad_notifier plugin.' -Rake::RDocTask.new(:rdoc) do |rdoc| - rdoc.rdoc_dir = 'rdoc' - rdoc.title = 'HoptoadNotifier' - rdoc.options << '--line-numbers' << '--inline-source' - rdoc.rdoc_files.include('README') - rdoc.rdoc_files.include('lib/**/*.rb') -end - desc 'Run ginger tests' task :ginger do $LOAD_PATH << File.join(*%w[vendor ginger lib]) @@ -28,3 +19,11 @@ task :ginger do ARGV << 'test' load File.join(*%w[vendor ginger bin ginger]) end + +begin + require 'yard' + YARD::Rake::YardocTask.new do |t| + t.files = ['lib/**/*.rb', 'TESTING.rdoc'] + end +rescue LoadError +end diff --git a/vendor/plugins/hoptoad_notifier/TESTING.rdoc b/vendor/plugins/hoptoad_notifier/TESTING.rdoc new file mode 100644 index 0000000..90ff40f --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/TESTING.rdoc @@ -0,0 +1,8 @@ += For Maintainers: + +When developing the Hoptoad Notifier, be sure to use the integration test +against an existing project on staging before pushing to master. + ++./script/integration_test.rb + + ++./script/integration_test.rb secure+ diff --git a/vendor/plugins/hoptoad_notifier/ginger_scenarios.rb b/vendor/plugins/hoptoad_notifier/ginger_scenarios.rb index 28b07f7..31de18f 100644 --- a/vendor/plugins/hoptoad_notifier/ginger_scenarios.rb +++ b/vendor/plugins/hoptoad_notifier/ginger_scenarios.rb @@ -14,8 +14,18 @@ Ginger.configure do |config| config.aliases["active_support"] = "activesupport" config.aliases["action_controller"] = "actionpack" + rails_1_2_6 = Ginger::Scenario.new + rails_1_2_6[/^active_?support$/] = "1.4.4" + rails_1_2_6[/^active_?record$/] = "1.15.6" + rails_1_2_6[/^action_?pack$/] = "1.13.6" + rails_1_2_6[/^action_?controller$/] = "1.13.6" + + config.scenarios << rails_1_2_6 config.scenarios << create_scenario("2.0.2") config.scenarios << create_scenario("2.1.2") config.scenarios << create_scenario("2.2.2") config.scenarios << create_scenario("2.3.2") + # Rails 2.3.3 has broken params filtering + # config.scenarios << create_scenario("2.3.3") + config.scenarios << create_scenario("2.3.4") end diff --git a/vendor/plugins/hoptoad_notifier/hoptoad_notifier.gemspec b/vendor/plugins/hoptoad_notifier/hoptoad_notifier.gemspec deleted file mode 100644 index febd9c3..0000000 --- a/vendor/plugins/hoptoad_notifier/hoptoad_notifier.gemspec +++ /dev/null @@ -1,21 +0,0 @@ -Gem::Specification.new do |s| - s.name = "hoptoad_notifier" - s.version = "1.1" - s.date = "2008-12-31" - s.summary = "Rails plugin that reports exceptions to Hoptoad." - s.email = "info@thoughtbot.com" - s.homepage = "http://github.com/thoughtbot/hoptoad_notifier" - s.description = "Rails plugin that reports exceptions to Hoptoad." - s.has_rdoc = true - s.authors = "Thoughtbot" - s.files = [ - "INSTALL", - "lib/hoptoad_notifier.rb", - "Rakefile", - "README", - "tasks/hoptoad_notifier_tasks.rake", - ] - s.test_files = [ - "test/hoptoad_notifier_test.rb" - ] -end diff --git a/vendor/plugins/hoptoad_notifier/init.rb b/vendor/plugins/hoptoad_notifier/init.rb new file mode 100644 index 0000000..6dbfa4c --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/init.rb @@ -0,0 +1 @@ +require File.join(File.dirname(__FILE__), 'rails', 'init') diff --git a/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier.rb b/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier.rb index 7d0da64..6dfc8c5 100644 --- a/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier.rb +++ b/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier.rb @@ -2,365 +2,149 @@ require 'net/http' require 'net/https' require 'rubygems' require 'active_support' +require 'hoptoad_notifier/configuration' +require 'hoptoad_notifier/notice' +require 'hoptoad_notifier/sender' +require 'hoptoad_notifier/catcher' +require 'hoptoad_notifier/backtrace' # Plugin for applications to automatically post errors to the Hoptoad of their choice. module HoptoadNotifier - IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound', - 'ActionController::RoutingError', - 'ActionController::InvalidAuthenticityToken', - 'CGI::Session::CookieStore::TamperedWithCookie', - 'ActionController::UnknownAction'] + VERSION = "2.0.18" + API_VERSION = "2.0" + LOG_PREFIX = "** [Hoptoad] " - # Some of these don't exist for Rails 1.2.*, so we have to consider that. - IGNORE_DEFAULT.map!{|e| eval(e) rescue nil }.compact! - IGNORE_DEFAULT.freeze - - IGNORE_USER_AGENT_DEFAULT = [] + HEADERS = { + 'Content-type' => 'text/xml', + 'Accept' => 'text/xml, application/xml' + } class << self - attr_accessor :host, :port, :secure, :api_key, :http_open_timeout, :http_read_timeout, - :proxy_host, :proxy_port, :proxy_user, :proxy_pass - - def backtrace_filters - @backtrace_filters ||= [] - end - - # Takes a block and adds it to the list of backtrace filters. When the filters - # run, the block will be handed each line of the backtrace and can modify - # it as necessary. For example, by default a path matching the RAILS_ROOT - # constant will be transformed into "[RAILS_ROOT]" - def filter_backtrace &block - self.backtrace_filters << block - end - - # The port on which your Hoptoad server runs. - def port - @port || (secure ? 443 : 80) - end - - # The host to connect to. - def host - @host ||= 'hoptoadapp.com' - end - - # The HTTP open timeout (defaults to 2 seconds). - def http_open_timeout - @http_open_timeout ||= 2 - end + # The sender object is responsible for delivering formatted data to the Hoptoad server. + # Must respond to #send_to_hoptoad. See HoptoadNotifier::Sender. + attr_accessor :sender - # The HTTP read timeout (defaults to 5 seconds). - def http_read_timeout - @http_read_timeout ||= 5 - end + # A Hoptoad configuration object. Must act like a hash and return sensible + # values for all Hoptoad configuration options. See HoptoadNotifier::Configuration. + attr_accessor :configuration - # Returns the list of errors that are being ignored. The array can be appended to. - def ignore - @ignore ||= (HoptoadNotifier::IGNORE_DEFAULT.dup) - @ignore.flatten! - @ignore + # Tell the log that the Notifier is good to go + def report_ready + write_verbose_log("Notifier #{VERSION} ready to catch errors") end - # Sets the list of ignored errors to only what is passed in here. This method - # can be passed a single error or a list of errors. - def ignore_only=(names) - @ignore = [names].flatten + # Prints out the environment info to the log for debugging help + def report_environment_info + write_verbose_log("Environment Info: #{environment_info}") end - # Returns the list of user agents that are being ignored. The array can be appended to. - def ignore_user_agent - @ignore_user_agent ||= (HoptoadNotifier::IGNORE_USER_AGENT_DEFAULT.dup) - @ignore_user_agent.flatten! - @ignore_user_agent + # Prints out the response body from Hoptoad for debugging help + def report_response_body(response) + write_verbose_log("Response from Hoptoad: \n#{response}") end - # Sets the list of ignored user agents to only what is passed in here. This method - # can be passed a single user agent or a list of user agents. - def ignore_user_agent_only=(names) - @ignore_user_agent = [names].flatten + # Returns the Ruby version, Rails version, and current Rails environment + def environment_info + info = "[Ruby: #{RUBY_VERSION}]" + info << " [Rails: #{::Rails::VERSION::STRING}]" if defined?(Rails) + info << " [Env: #{configuration.environment_name}]" end - # Returns a list of parameters that should be filtered out of what is sent to Hoptoad. - # By default, all "password" attributes will have their contents replaced. - def params_filters - @params_filters ||= %w(password) + # Writes out the given message to the #logger + def write_verbose_log(message) + logger.info LOG_PREFIX + message if logger end - def environment_filters - @environment_filters ||= %w() + # Look for the Rails logger currently defined + def logger + if defined?(Rails.logger) + Rails.logger + elsif defined?(RAILS_DEFAULT_LOGGER) + RAILS_DEFAULT_LOGGER + end end # Call this method to modify defaults in your initializers. # - # HoptoadNotifier.configure do |config| - # config.api_key = '1234567890abcdef' - # config.secure = false - # end + # @example + # HoptoadNotifier.configure do |config| + # config.api_key = '1234567890abcdef' + # config.secure = false + # end + def configure(silent = false) + self.configuration ||= Configuration.new + yield(configuration) + self.sender = Sender.new(configuration) + report_ready unless silent + end + + # Sends an exception manually using this method, even when you are not in a controller. # - # NOTE: secure connections are not yet supported. - def configure - add_default_filters - yield self - if defined?(ActionController::Base) && !ActionController::Base.include?(HoptoadNotifier::Catcher) - ActionController::Base.send(:include, HoptoadNotifier::Catcher) - end - end - - def protocol #:nodoc: - secure ? "https" : "http" - end - - def url #:nodoc: - URI.parse("#{protocol}://#{host}:#{port}/notices/") - end - - def default_notice_options #:nodoc: - { - :api_key => HoptoadNotifier.api_key, - :error_message => 'Notification', - :backtrace => caller, - :request => {}, - :session => {}, - :environment => ENV.to_hash - } - end - - # You can send an exception manually using this method, even when you are not in a - # controller. You can pass an exception or a hash that contains the attributes that - # would be sent to Hoptoad: - # * api_key: The API key for this project. The API key is a unique identifier that Hoptoad - # uses for identification. - # * error_message: The error returned by the exception (or the message you want to log). - # * backtrace: A backtrace, usually obtained with +caller+. - # * request: The controller's request object. - # * session: The contents of the user's session. - # * environment: ENV merged with the contents of the request's environment. - def notify notice = {} - Sender.new.notify_hoptoad( notice ) + # @param [Exception] exception The exception you want to notify Hoptoad about. + # @param [Hash] opts Data that will be sent to Hoptoad. + # + # @option opts [String] :api_key The API key for this project. The API key is a unique identifier that Hoptoad uses for identification. + # @option opts [String] :error_message The error returned by the exception (or the message you want to log). + # @option opts [String] :backtrace A backtrace, usually obtained with +caller+. + # @option opts [String] :request The controller's request object. + # @option opts [String] :session The contents of the user's session. + # @option opts [String] :environment ENV merged with the contents of the request's environment. + def notify(exception, opts = {}) + send_notice(build_notice_for(exception, opts)) end - def add_default_filters - self.backtrace_filters.clear - - filter_backtrace do |line| - line.gsub(/#{RAILS_ROOT}/, "[RAILS_ROOT]") - end - - filter_backtrace do |line| - line.gsub(/^\.\//, "") - end - - filter_backtrace do |line| - if defined?(Gem) - Gem.path.inject(line) do |line, path| - line.gsub(/#{path}/, "[GEM_ROOT]") - end - end - end - - filter_backtrace do |line| - line if line !~ /lib\/#{File.basename(__FILE__)}/ - end + # Sends the notice unless it is one of the default ignored exceptions + # @see HoptoadNotifier.notify + def notify_or_ignore(exception, opts = {}) + notice = build_notice_for(exception, opts) + send_notice(notice) unless notice.ignore? end - end - # Include this module in Controllers in which you want to be notified of errors. - module Catcher + def build_lookup_hash_for(exception, options = {}) + notice = build_notice_for(exception, options) - def self.included(base) #:nodoc: - if base.instance_methods.include? 'rescue_action_in_public' and !base.instance_methods.include? 'rescue_action_in_public_without_hoptoad' - base.send(:alias_method, :rescue_action_in_public_without_hoptoad, :rescue_action_in_public) - base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_hoptoad) - end - end + result = {} + result[:action] = notice.action rescue nil + result[:component] = notice.component rescue nil + result[:error_class] = notice.error_class if notice.error_class + result[:environment_name] = 'production' - # Overrides the rescue_action method in ActionController::Base, but does not inhibit - # any custom processing that is defined with Rails 2's exception helpers. - def rescue_action_in_public_with_hoptoad exception - notify_hoptoad(exception) unless ignore?(exception) || ignore_user_agent? - rescue_action_in_public_without_hoptoad(exception) - end - - # This method should be used for sending manual notifications while you are still - # inside the controller. Otherwise it works like HoptoadNotifier.notify. - def notify_hoptoad hash_or_exception - if public_environment? - notice = normalize_notice(hash_or_exception) - notice = clean_notice(notice) - send_to_hoptoad(:notice => notice) + unless notice.backtrace.lines.empty? + result[:file] = notice.backtrace.lines.first.file + result[:line_number] = notice.backtrace.lines.first.number end - end - alias_method :inform_hoptoad, :notify_hoptoad - - # Returns the default logger or a logger that prints to STDOUT. Necessary for manual - # notifications outside of controllers. - def logger - ActiveRecord::Base.logger - rescue - @logger ||= Logger.new(STDERR) + result end private - def public_environment? #nodoc: - defined?(RAILS_ENV) and !['development', 'test'].include?(RAILS_ENV) - end - - def ignore?(exception) #:nodoc: - ignore_these = HoptoadNotifier.ignore.flatten - ignore_these.include?(exception.class) || ignore_these.include?(exception.class.name) - end - - def ignore_user_agent? #:nodoc: - HoptoadNotifier.ignore_user_agent.flatten.any? { |ua| ua === request.user_agent } - end - - def exception_to_data exception #:nodoc: - data = { - :api_key => HoptoadNotifier.api_key, - :error_class => exception.class.name, - :error_message => "#{exception.class.name}: #{exception.message}", - :backtrace => exception.backtrace, - :environment => ENV.to_hash - } - - if self.respond_to? :request - data[:request] = { - :params => request.parameters.to_hash, - :rails_root => File.expand_path(RAILS_ROOT), - :url => "#{request.protocol}#{request.host}#{request.request_uri}" - } - data[:environment].merge!(request.env.to_hash) - end - - if self.respond_to? :session - data[:session] = { - :key => session.instance_variable_get("@session_id"), - :data => session.respond_to?(:to_hash) ? - session.to_hash : - session.instance_variable_get("@data") - } + def send_notice(notice) + if configuration.public? + sender.send_to_hoptoad(notice.to_xml) end - - data end - def normalize_notice(notice) #:nodoc: - case notice - when Hash - HoptoadNotifier.default_notice_options.merge(notice) - when Exception - HoptoadNotifier.default_notice_options.merge(exception_to_data(notice)) - end - end - - def clean_notice(notice) #:nodoc: - notice[:backtrace] = clean_hoptoad_backtrace(notice[:backtrace]) - if notice[:request].is_a?(Hash) && notice[:request][:params].is_a?(Hash) - notice[:request][:params] = filter_parameters(notice[:request][:params]) if respond_to?(:filter_parameters) - notice[:request][:params] = clean_hoptoad_params(notice[:request][:params]) - end - if notice[:environment].is_a?(Hash) - notice[:environment] = filter_parameters(notice[:environment]) if respond_to?(:filter_parameters) - notice[:environment] = clean_hoptoad_environment(notice[:environment]) - end - clean_non_serializable_data(notice) - end - - def send_to_hoptoad data #:nodoc: - headers = { - 'Content-type' => 'application/x-yaml', - 'Accept' => 'text/xml, application/xml' - } - - url = HoptoadNotifier.url - - http = Net::HTTP::Proxy(HoptoadNotifier.proxy_host, - HoptoadNotifier.proxy_port, - HoptoadNotifier.proxy_user, - HoptoadNotifier.proxy_pass).new(url.host, url.port) - - http.use_ssl = true - http.read_timeout = HoptoadNotifier.http_read_timeout - http.open_timeout = HoptoadNotifier.http_open_timeout - http.use_ssl = !!HoptoadNotifier.secure - - response = begin - http.post(url.path, stringify_keys(data).to_yaml, headers) - rescue TimeoutError => e - logger.error "Timeout while contacting the Hoptoad server." if logger - nil - end - - case response - when Net::HTTPSuccess then - logger.info "Hoptoad Success: #{response.class}" if logger + def build_notice_for(exception, opts = {}) + exception = unwrap_exception(exception) + if exception.respond_to?(:to_hash) + opts = opts.merge(exception) else - logger.error "Hoptoad Failure: #{response.class}\n#{response.body if response.respond_to? :body}" if logger - end - end - - def clean_hoptoad_backtrace backtrace #:nodoc: - if backtrace.to_a.size == 1 - backtrace = backtrace.to_a.first.split(/\n\s*/) - end - - filtered = backtrace.to_a.map do |line| - HoptoadNotifier.backtrace_filters.inject(line) do |line, proc| - proc.call(line) - end + opts = opts.merge(:exception => exception) end - - filtered.compact + Notice.new(configuration.merge(opts)) end - def clean_hoptoad_params params #:nodoc: - params.each do |k, v| - params[k] = "[FILTERED]" if HoptoadNotifier.params_filters.any? do |filter| - k.to_s.match(/#{filter}/) - end - end - end - - def clean_hoptoad_environment env #:nodoc: - env.each do |k, v| - env[k] = "[FILTERED]" if HoptoadNotifier.environment_filters.any? do |filter| - k.to_s.match(/#{filter}/) - end - end - end - - def clean_non_serializable_data(notice) #:nodoc: - notice.select{|k,v| serializable?(v) }.inject({}) do |h, pair| - h[pair.first] = pair.last.is_a?(Hash) ? clean_non_serializable_data(pair.last) : pair.last - h - end - end - - def serializable?(value) #:nodoc: - value.is_a?(Fixnum) || - value.is_a?(Array) || - value.is_a?(String) || - value.is_a?(Hash) || - value.is_a?(Bignum) - end - - def stringify_keys(hash) #:nodoc: - hash.inject({}) do |h, pair| - h[pair.first.to_s] = pair.last.is_a?(Hash) ? stringify_keys(pair.last) : pair.last - h + def unwrap_exception(exception) + if exception.respond_to?(:original_exception) + exception.original_exception + elsif exception.respond_to?(:continued_exception) + exception.continued_exception + else + exception end end - - end - - # A dummy class for sending notifications manually outside of a controller. - class Sender - def rescue_action_in_public(exception) - end - - include HoptoadNotifier::Catcher end end diff --git a/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/backtrace.rb b/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/backtrace.rb new file mode 100644 index 0000000..617fd3c --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/backtrace.rb @@ -0,0 +1,99 @@ +module HoptoadNotifier + # Front end to parsing the backtrace for each notice + class Backtrace + + # Handles backtrace parsing line by line + class Line + + INPUT_FORMAT = %r{^([^:]+):(\d+)(?::in `([^']+)')?$}.freeze + + # The file portion of the line (such as app/models/user.rb) + attr_reader :file + + # The line number portion of the line + attr_reader :number + + # The method of the line (such as index) + attr_reader :method + + # Parses a single line of a given backtrace + # @param [String] unparsed_line The raw line from +caller+ or some backtrace + # @return [Line] The parsed backtrace line + def self.parse(unparsed_line) + _, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a + new(file, number, method) + end + + def initialize(file, number, method) + self.file = file + self.number = number + self.method = method + end + + # Reconstructs the line in a readable fashion + def to_s + "#{file}:#{number}:in `#{method}'" + end + + def ==(other) + to_s == other.to_s + end + + def inspect + "" + end + + private + + attr_writer :file, :number, :method + end + + # holder for an Array of Backtrace::Line instances + attr_reader :lines + + def self.parse(ruby_backtrace, opts = {}) + ruby_lines = split_multiline_backtrace(ruby_backtrace) + + filters = opts[:filters] || [] + filtered_lines = ruby_lines.to_a.map do |line| + filters.inject(line) do |line, proc| + proc.call(line) + end + end.compact + + lines = filtered_lines.collect do |unparsed_line| + Line.parse(unparsed_line) + end + + instance = new(lines) + end + + def initialize(lines) + self.lines = lines + end + + def inspect + "" + end + + def ==(other) + if other.respond_to?(:lines) + lines == other.lines + else + false + end + end + + private + + attr_writer :lines + + def self.split_multiline_backtrace(backtrace) + if backtrace.to_a.size == 1 + backtrace.to_a.first.split(/\n\s*/) + else + backtrace + end + end + end +end diff --git a/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/catcher.rb b/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/catcher.rb new file mode 100644 index 0000000..3e7c66c --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/catcher.rb @@ -0,0 +1,95 @@ +module HoptoadNotifier + # Include this module in Controllers in which you want to be notified of errors. + module Catcher + + # Sets up an alias chain to catch exceptions when Rails does + def self.included(base) #:nodoc: + 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' + base.send(:alias_method, :rescue_action_in_public_without_hoptoad, :rescue_action_in_public) + base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_hoptoad) + base.send(:alias_method, :rescue_action_locally_without_hoptoad, :rescue_action_locally) + base.send(:alias_method, :rescue_action_locally, :rescue_action_locally_with_hoptoad) + end + end + + private + + # Overrides the rescue_action method in ActionController::Base, but does not inhibit + # any custom processing that is defined with Rails 2's exception helpers. + def rescue_action_in_public_with_hoptoad(exception) + unless hoptoad_ignore_user_agent? + HoptoadNotifier.notify_or_ignore(exception, hoptoad_request_data) + end + rescue_action_in_public_without_hoptoad(exception) + end + + def rescue_action_locally_with_hoptoad(exception) + result = rescue_action_locally_without_hoptoad(exception) + + if HoptoadNotifier.configuration.development_lookup + path = File.join(File.dirname(__FILE__), '..', 'templates', 'rescue.erb') + notice = HoptoadNotifier.build_lookup_hash_for(exception, hoptoad_request_data) + + result << @template.render( + :file => path, + :use_full_path => false, + :locals => { :host => HoptoadNotifier.configuration.host, + :api_key => HoptoadNotifier.configuration.api_key, + :notice => notice }) + end + + result + end + + # This method should be used for sending manual notifications while you are still + # inside the controller. Otherwise it works like HoptoadNotifier.notify. + def notify_hoptoad(hash_or_exception) + unless consider_all_requests_local || local_request? + HoptoadNotifier.notify(hash_or_exception, hoptoad_request_data) + end + end + + def hoptoad_ignore_user_agent? #:nodoc: + # Rails 1.2.6 doesn't have request.user_agent, so check for it here + user_agent = request.respond_to?(:user_agent) ? request.user_agent : request.env["HTTP_USER_AGENT"] + HoptoadNotifier.configuration.ignore_user_agent.flatten.any? { |ua| ua === user_agent } + end + + def hoptoad_request_data + { :parameters => hoptoad_filter_if_filtering(params.to_hash), + :session_data => hoptoad_session_data, + :controller => params[:controller], + :action => params[:action], + :url => hoptoad_request_url, + :cgi_data => hoptoad_filter_if_filtering(request.env), + :environment_vars => hoptoad_filter_if_filtering(ENV) } + end + + def hoptoad_filter_if_filtering(hash) + if respond_to?(:filter_parameters) + filter_parameters(hash) rescue hash + else + hash + end + end + + def hoptoad_session_data + if session.respond_to?(:to_hash) + session.to_hash + else + session.data + end + end + + def hoptoad_request_url + url = "#{request.protocol}#{request.host}" + + unless [80, 443].include?(request.port) + url << ":#{request.port}" + end + + url << request.request_uri + url + end + end +end diff --git a/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/configuration.rb b/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/configuration.rb new file mode 100644 index 0000000..3091594 --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/configuration.rb @@ -0,0 +1,229 @@ +module HoptoadNotifier + # Used to set up and modify settings for the notifier. + class Configuration + + OPTIONS = [:api_key, :backtrace_filters, :development_environments, + :development_lookup, :environment_name, :host, + :http_open_timeout, :http_read_timeout, :ignore, :ignore_by_filters, + :ignore_user_agent, :notifier_name, :notifier_url, :notifier_version, + :params_filters, :project_root, :port, :protocol, :proxy_host, + :proxy_pass, :proxy_port, :proxy_user, :secure].freeze + + # The API key for your project, found on the project edit form. + attr_accessor :api_key + + # The host to connect to (defaults to hoptoadapp.com). + attr_accessor :host + + # The port on which your Hoptoad server runs (defaults to 443 for secure + # connections, 80 for insecure connections). + attr_accessor :port + + # +true+ for https connections, +false+ for http connections. + attr_accessor :secure + + # The HTTP open timeout in seconds (defaults to 2). + attr_accessor :http_open_timeout + + # The HTTP read timeout in seconds (defaults to 5). + attr_accessor :http_read_timeout + + # The hostname of your proxy server (if using a proxy) + attr_accessor :proxy_host + + # The port of your proxy server (if using a proxy) + attr_accessor :proxy_port + + # The username to use when logging into your proxy server (if using a proxy) + attr_accessor :proxy_user + + # The password to use when logging into your proxy server (if using a proxy) + attr_accessor :proxy_pass + + # A list of parameters that should be filtered out of what is sent to Hoptoad. + # By default, all "password" attributes will have their contents replaced. + attr_reader :params_filters + + # A list of filters for cleaning and pruning the backtrace. See #filter_backtrace. + attr_reader :backtrace_filters + + # A list of filters for ignoring exceptions. See #ignore_by_filter. + attr_reader :ignore_by_filters + + # A list of exception classes to ignore. The array can be appended to. + attr_reader :ignore + + # A list of user agents that are being ignored. The array can be appended to. + attr_reader :ignore_user_agent + + # A list of environments in which notifications should not be sent. + attr_accessor :development_environments + + # +true+ if you want to check for production errors matching development errors, +false+ otherwise. + attr_accessor :development_lookup + + # The name of the environment the application is running in + attr_accessor :environment_name + + # The path to the project in which the error occurred, such as the RAILS_ROOT + attr_accessor :project_root + + # The name of the notifier library being used to send notifications (such as "Hoptoad Notifier") + attr_accessor :notifier_name + + # The version of the notifier library being used to send notifications (such as "1.0.2") + attr_accessor :notifier_version + + # The url of the notifier library being used to send notifications + attr_accessor :notifier_url + + DEFAULT_PARAMS_FILTERS = %w(password password_confirmation).freeze + + DEFAULT_BACKTRACE_FILTERS = [ + lambda { |line| + if defined?(HoptoadNotifier.configuration.project_root) + line.gsub(/#{HoptoadNotifier.configuration.project_root}/, "[PROJECT_ROOT]") + else + line + end + }, + lambda { |line| line.gsub(/^\.\//, "") }, + lambda { |line| + if defined?(Gem) + Gem.path.inject(line) do |line, path| + line.gsub(/#{path}/, "[GEM_ROOT]") + end + end + }, + lambda { |line| line if line !~ %r{lib/hoptoad_notifier} } + ].freeze + + IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound', + 'ActionController::RoutingError', + 'ActionController::InvalidAuthenticityToken', + 'CGI::Session::CookieStore::TamperedWithCookie', + 'ActionController::UnknownAction'] + + # Some of these don't exist for Rails 1.2.*, so we have to consider that. + IGNORE_DEFAULT.map!{|e| eval(e) rescue nil }.compact! + IGNORE_DEFAULT.freeze + + alias_method :secure?, :secure + + def initialize + @secure = false + @host = 'hoptoadapp.com' + @http_open_timeout = 2 + @http_read_timeout = 5 + @params_filters = DEFAULT_PARAMS_FILTERS.dup + @backtrace_filters = DEFAULT_BACKTRACE_FILTERS.dup + @ignore_by_filters = [] + @ignore = IGNORE_DEFAULT.dup + @ignore_user_agent = [] + @development_environments = %w(development test cucumber) + @development_lookup = true + @notifier_name = 'Hoptoad Notifier' + @notifier_version = VERSION + @notifier_url = 'http://hoptoadapp.com' + end + + # Takes a block and adds it to the list of backtrace filters. When the filters + # run, the block will be handed each line of the backtrace and can modify + # it as necessary. + # + # @example + # config.filter_bracktrace do |line| + # line.gsub(/^#{Rails.root}/, "[RAILS_ROOT]") + # end + # + # @param [Proc] block The new backtrace filter. + # @yieldparam [String] line A line in the backtrace. + def filter_backtrace(&block) + self.backtrace_filters << block + end + + # Takes a block and adds it to the list of ignore filters. + # When the filters run, the block will be handed the exception. + # @example + # config.ignore_by_filter do |exception_data| + # true if exception_data[:error_class] == "RuntimeError" + # end + # + # @param [Proc] block The new ignore filter + # @yieldparam [Hash] data The exception data given to +HoptoadNotifier.notify+ + # @yieldreturn [Boolean] If the block returns true the exception will be ignored, otherwise it will be processed by hoptoad. + def ignore_by_filter(&block) + self.ignore_by_filters << block + end + + # Overrides the list of default ignored errors. + # + # @param [Array] names A list of exceptions to ignore. + def ignore_only=(names) + @ignore = [names].flatten + end + + # Overrides the list of default ignored user agents + # + # @param [Array] A list of user agents to ignore + def ignore_user_agent_only=(names) + @ignore_user_agent = [names].flatten + end + + # Allows config options to be read like a hash + # + # @param [Symbol] option Key for a given attribute + def [](option) + send(option) + end + + # Returns a hash of all configurable options + def to_hash + OPTIONS.inject({}) do |hash, option| + hash.merge(option.to_sym => send(option)) + end + end + + # Returns a hash of all configurable options merged with +hash+ + # + # @param [Hash] hash A set of configuration options that will take precedence over the defaults + def merge(hash) + to_hash.merge(hash) + end + + # Determines if the notifier will send notices. + # @return [Boolean] Returns +false+ if in a development environment, +true+ otherwise. + def public? + !development_environments.include?(environment_name) + end + + def port + @port || default_port + end + + def protocol + if secure? + 'https' + else + 'http' + end + end + + def environment_filters + warn 'config.environment_filters has been deprecated and has no effect.' + [] + end + + private + + def default_port + if secure? + 443 + else + 80 + end + end + + end + +end diff --git a/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/notice.rb b/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/notice.rb new file mode 100644 index 0000000..1336f68 --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/notice.rb @@ -0,0 +1,295 @@ +module HoptoadNotifier + class Notice + + # The exception that caused this notice, if any + attr_reader :exception + + # The API key for the project to which this notice should be sent + attr_reader :api_key + + # The backtrace from the given exception or hash. + attr_reader :backtrace + + # The name of the class of error (such as RuntimeError) + attr_reader :error_class + + # The name of the server environment (such as "production") + attr_reader :environment_name + + # CGI variables such as HTTP_METHOD + attr_reader :cgi_data + + # The message from the exception, or a general description of the error + attr_reader :error_message + + # See Configuration#backtrace_filters + attr_reader :backtrace_filters + + # See Configuration#params_filters + attr_reader :params_filters + + # A hash of parameters from the query string or post body. + attr_reader :parameters + alias_method :params, :parameters + + # The component (if any) which was used in this request (usually the controller) + attr_reader :component + alias_method :controller, :component + + # The action (if any) that was called in this request + attr_reader :action + + # A hash of session data from the request + attr_reader :session_data + + # The path to the project that caused the error (usually RAILS_ROOT) + attr_reader :project_root + + # The URL at which the error occurred (if any) + attr_reader :url + + # See Configuration#ignore + attr_reader :ignore + + # See Configuration#ignore_by_filters + attr_reader :ignore_by_filters + + # The name of the notifier library sending this notice, such as "Hoptoad Notifier" + attr_reader :notifier_name + + # The version number of the notifier library sending this notice, such as "2.1.3" + attr_reader :notifier_version + + # A URL for more information about the notifier library sending this notice + attr_reader :notifier_url + + def initialize(args) + self.args = args + self.exception = args[:exception] + self.api_key = args[:api_key] + self.project_root = args[:project_root] + self.url = args[:url] + + self.notifier_name = args[:notifier_name] + self.notifier_version = args[:notifier_version] + self.notifier_url = args[:notifier_url] + + self.ignore = args[:ignore] || [] + self.ignore_by_filters = args[:ignore_by_filters] || [] + self.backtrace_filters = args[:backtrace_filters] || [] + self.params_filters = args[:params_filters] || [] + self.parameters = args[:parameters] || {} + self.component = args[:component] || args[:controller] + self.action = args[:action] + + self.environment_name = args[:environment_name] + self.cgi_data = args[:cgi_data] + self.backtrace = Backtrace.parse(exception_attribute(:backtrace, caller)) + self.error_class = exception_attribute(:error_class) {|exception| exception.class.name } + self.error_message = exception_attribute(:error_message, 'Notification') do |exception| + "#{exception.class.name}: #{exception.message}" + end + + find_session_data + clean_params + end + + # Converts the given notice to XML + def to_xml + builder = Builder::XmlMarkup.new + builder.instruct! + xml = builder.notice(:version => HoptoadNotifier::API_VERSION) do |notice| + notice.tag!("api-key", api_key) + notice.notifier do |notifier| + notifier.name(notifier_name) + notifier.version(notifier_version) + notifier.url(notifier_url) + end + notice.error do |error| + error.tag!('class', error_class) + error.message(error_message) + error.backtrace do |backtrace| + self.backtrace.lines.each do |line| + backtrace.line(:number => line.number, + :file => line.file, + :method => line.method) + end + end + end + if url || + controller || + action || + !parameters.blank? || + !cgi_data.blank? || + !session_data.blank? + notice.request do |request| + request.url(url) + request.component(controller) + request.action(action) + unless parameters.blank? + request.params do |params| + xml_vars_for(params, parameters) + end + end + unless session_data.blank? + request.session do |session| + xml_vars_for(session, session_data) + end + end + unless cgi_data.blank? + request.tag!("cgi-data") do |cgi_datum| + xml_vars_for(cgi_datum, cgi_data) + end + end + end + end + notice.tag!("server-environment") do |env| + env.tag!("project-root", project_root) + env.tag!("environment-name", environment_name) + end + end + xml.to_s + end + + # Determines if this notice should be ignored + def ignore? + ignored_class_names.include?(error_class) || + ignore_by_filters.any? {|filter| filter.call(self) } + end + + # Allows properties to be accessed using a hash-like syntax + # + # @example + # notice[:error_message] + # @param [String] method The given key for an attribute + # @return The attribute value, or self if given +:request+ + def [](method) + case method + when :request + self + else + send(method) + end + end + + private + + attr_writer :exception, :api_key, :backtrace, :error_class, :error_message, + :backtrace_filters, :parameters, :params_filters, + :environment_filters, :session_data, :project_root, :url, :ignore, + :ignore_by_filters, :notifier_name, :notifier_url, :notifier_version, + :component, :action, :cgi_data, :environment_name + + # Arguments given in the initializer + attr_accessor :args + + # Gets a property named +attribute+ of an exception, either from an actual + # exception or a hash. + # + # If an exception is available, #from_exception will be used. Otherwise, + # a key named +attribute+ will be used from the #args. + # + # If no exception or hash key is available, +default+ will be used. + def exception_attribute(attribute, default = nil, &block) + (exception && from_exception(attribute, &block)) || args[attribute] || default + end + + # Gets a property named +attribute+ from an exception. + # + # If a block is given, it will be used when getting the property from an + # exception. The block should accept and exception and return the value for + # the property. + # + # If no block is given, a method with the same name as +attribute+ will be + # invoked for the value. + def from_exception(attribute) + if block_given? + yield(exception) + else + exception.send(attribute) + end + end + + # Removes non-serializable data from the given attribute. + # See #clean_unserializable_data + def clean_unserializable_data_from(attribute) + self.send(:"#{attribute}=", clean_unserializable_data(send(attribute))) + end + + # Removes non-serializable data. Allowed data types are strings, arrays, + # and hashes. All other types are converted to strings. + # TODO: move this onto Hash + def clean_unserializable_data(data) + if data.respond_to?(:to_hash) + data.to_hash.inject({}) do |result, (key, value)| + result.merge(key => clean_unserializable_data(value)) + end + elsif data.respond_to?(:to_ary) + data.collect do |value| + clean_unserializable_data(value) + end + else + data.to_s + end + end + + # Replaces the contents of params that match params_filters. + # TODO: extract this to a different class + def clean_params + clean_unserializable_data_from(:parameters) + filter(parameters) + if cgi_data + clean_unserializable_data_from(:cgi_data) + filter(cgi_data) + end + if session_data + clean_unserializable_data_from(:session_data) + end + end + + def filter(hash) + if params_filters + hash.each do |key, value| + if filter_key?(key) + hash[key] = "[FILTERED]" + elsif value.respond_to?(:to_hash) + filter(hash[key]) + end + end + end + end + + def filter_key?(key) + params_filters.any? do |filter| + key.to_s.include?(filter) + end + end + + def find_session_data + self.session_data = args[:session_data] || args[:session] || {} + self.session_data = session_data[:data] if session_data[:data] + end + + # Converts the mixed class instances and class names into just names + # TODO: move this into Configuration or another class + def ignored_class_names + ignore.collect do |string_or_class| + if string_or_class.respond_to?(:name) + string_or_class.name + else + string_or_class + end + end + end + + def xml_vars_for(builder, hash) + hash.each do |key, value| + if value.respond_to?(:to_hash) + builder.var(:key => key){|b| xml_vars_for(b, value.to_hash) } + else + builder.var(value.to_s, :key => key) + end + end + end + end +end diff --git a/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/sender.rb b/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/sender.rb new file mode 100644 index 0000000..94bb3f0 --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/lib/hoptoad_notifier/sender.rb @@ -0,0 +1,63 @@ +module HoptoadNotifier + # Sends out the notice to Hoptoad + class Sender + + NOTICES_URI = '/notifier_api/v2/notices/'.freeze + + def initialize(options = {}) + [:proxy_host, :proxy_port, :proxy_user, :proxy_pass, :protocol, + :host, :port, :secure, :http_open_timeout, :http_read_timeout].each do |option| + instance_variable_set("@#{option}", options[option]) + end + end + + # Sends the notice data off to Hoptoad for processing. + # + # @param [String] data The XML notice to be sent off + def send_to_hoptoad(data) + logger.debug { "Sending request to #{url.to_s}:\n#{data}" } + + http = + Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass). + new(url.host, url.port) + + http.read_timeout = http_read_timeout + http.open_timeout = http_open_timeout + http.use_ssl = secure + + response = begin + http.post(url.path, data, HEADERS) + rescue TimeoutError => e + log :error, "Timeout while contacting the Hoptoad server." + nil + end + + case response + when Net::HTTPSuccess then + log :info, "Success: #{response.class}", response + else + log :error, "Failure: #{response.class}", response + end + end + + private + + attr_reader :proxy_host, :proxy_port, :proxy_user, :proxy_pass, :protocol, + :host, :port, :secure, :http_open_timeout, :http_read_timeout + + def url + URI.parse("#{protocol}://#{host}:#{port}").merge(NOTICES_URI) + end + + def log(level, message, response = nil) + logger.send level, LOG_PREFIX + message if logger + HoptoadNotifier.report_environment_info + HoptoadNotifier.report_response_body(response.body) if response && response.respond_to?(:body) + end + + def logger + HoptoadNotifier.logger + end + + end +end diff --git a/vendor/plugins/hoptoad_notifier/lib/hoptoad_tasks.rb b/vendor/plugins/hoptoad_notifier/lib/hoptoad_tasks.rb index 2b51c8b..0cb7213 100644 --- a/vendor/plugins/hoptoad_notifier/lib/hoptoad_tasks.rb +++ b/vendor/plugins/hoptoad_notifier/lib/hoptoad_tasks.rb @@ -2,9 +2,19 @@ require 'net/http' require 'uri' require 'active_support' +# Capistrano tasks for notifying Hoptoad of deploys module HoptoadTasks + + # Alerts Hoptoad of a deploy. + # + # @param [Hash] opts Data about the deploy that is set to Hoptoad + # + # @option opts [String] :rails_env Environment of the deploy (production, staging) + # @option opts [String] :scm_revision The given revision/sha that is being deployed + # @option opts [String] :scm_repository Address of your repository to help with code lookups + # @option opts [String] :local_username Who is deploying def self.deploy(opts = {}) - if HoptoadNotifier.api_key.blank? + if HoptoadNotifier.configuration.api_key.blank? puts "I don't seem to be configured with an API key. Please check your configuration." return false end @@ -14,10 +24,11 @@ module HoptoadTasks return false end - params = {:api_key => HoptoadNotifier.api_key} + params = {'api_key' => opts.delete(:api_key) || + HoptoadNotifier.configuration.api_key} opts.each {|k,v| params["deploy[#{k}]"] = v } - url = URI.parse("http://#{HoptoadNotifier.host}/deploys.txt") + url = URI.parse("http://#{HoptoadNotifier.configuration.host || 'hoptoadapp.com'}/deploys.txt") response = Net::HTTP.post_form(url, params) puts response.body return Net::HTTPSuccess === response diff --git a/vendor/plugins/hoptoad_notifier/lib/templates/rescue.erb b/vendor/plugins/hoptoad_notifier/lib/templates/rescue.erb new file mode 100644 index 0000000..fe5919e --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/lib/templates/rescue.erb @@ -0,0 +1,91 @@ + + + diff --git a/vendor/plugins/hoptoad_notifier/rails/init.rb b/vendor/plugins/hoptoad_notifier/rails/init.rb new file mode 100644 index 0000000..e21fb62 --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/rails/init.rb @@ -0,0 +1,8 @@ +if defined?(ActionController::Base) && !ActionController::Base.include?(HoptoadNotifier::Catcher) + ActionController::Base.send(:include, HoptoadNotifier::Catcher) +end + +HoptoadNotifier.configure(true) do |config| + config.environment_name = RAILS_ENV + config.project_root = RAILS_ROOT +end diff --git a/vendor/plugins/hoptoad_notifier/recipes/hoptoad.rb b/vendor/plugins/hoptoad_notifier/recipes/hoptoad.rb index fbd4549..c196c21 100644 --- a/vendor/plugins/hoptoad_notifier/recipes/hoptoad.rb +++ b/vendor/plugins/hoptoad_notifier/recipes/hoptoad.rb @@ -6,14 +6,17 @@ # # Defines deploy:notify_hoptoad which will send information about the deploy to Hoptoad. # -after "deploy:cleanup", "deploy:notify_hoptoad" +after "deploy", "deploy:notify_hoptoad" +after "deploy:migrations", "deploy:notify_hoptoad" namespace :deploy do desc "Notify Hoptoad of the deployment" - task :notify_hoptoad do - rails_env = fetch(:rails_env, "production") + task :notify_hoptoad, :except => { :no_release => true } do + rails_env = fetch(:hoptoad_env, fetch(:rails_env, "production")) local_user = ENV['USER'] || ENV['USERNAME'] - notify_command = "rake RAILS_ENV=#{rails_env} hoptoad:deploy TO=#{rails_env} REVISION=#{current_revision} REPO=#{repository} USER=#{local_user}" + executable = RUBY_PLATFORM.downcase.include?('mswin') ? 'rake.bat' : 'rake' + notify_command = "#{executable} hoptoad:deploy TO=#{rails_env} REVISION=#{current_revision} REPO=#{repository} USER=#{local_user}" + notify_command << " API_KEY=#{ENV['API_KEY']}" if ENV['API_KEY'] puts "Notifying Hoptoad of Deploy (#{notify_command})" `#{notify_command}` puts "Hoptoad Notification Complete." diff --git a/vendor/plugins/hoptoad_notifier/script/integration_test.rb b/vendor/plugins/hoptoad_notifier/script/integration_test.rb index 43e1e5f..b5f76bc 100755 --- a/vendor/plugins/hoptoad_notifier/script/integration_test.rb +++ b/vendor/plugins/hoptoad_notifier/script/integration_test.rb @@ -1,11 +1,17 @@ #!/usr/bin/env ruby -require File.join(File.dirname(__FILE__), "..", "lib", "hoptoad_notifier") - -fail "Please supply an API Key as the first argument" if ARGV.empty? +require 'logger' +require 'fileutils' RAILS_ENV = "production" -RAILS_ROOT = "./" +RAILS_ROOT = FileUtils.pwd +RAILS_DEFAULT_LOGGER = Logger.new(STDOUT) + +$: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) +require 'hoptoad_notifier' +require 'rails/init' + +fail "Please supply an API Key as the first argument" if ARGV.empty? host = ARGV[1] host ||= "hoptoadapp.com" @@ -23,6 +29,10 @@ HoptoadNotifier.configure do |config| config.host = host config.api_key = ARGV.first end -puts "Sending #{secure or "in"}secure notification to project with key #{ARGV.first}" +puts "Configuration:" +HoptoadNotifier.configuration.to_hash.each do |key, value| + puts sprintf("%25s: %s", key.to_s, value.inspect.slice(0, 55)) +end +puts "Sending #{secure ? "" : "in"}secure notification to project with key #{ARGV.first}" HoptoadNotifier.notify(exception) diff --git a/vendor/plugins/hoptoad_notifier/tasks/hoptoad_notifier_tasks.rake b/vendor/plugins/hoptoad_notifier/tasks/hoptoad_notifier_tasks.rake index badfa51..ececd35 100644 --- a/vendor/plugins/hoptoad_notifier/tasks/hoptoad_notifier_tasks.rake +++ b/vendor/plugins/hoptoad_notifier/tasks/hoptoad_notifier_tasks.rake @@ -5,24 +5,34 @@ namespace :hoptoad do HoptoadTasks.deploy(:rails_env => ENV['TO'], :scm_revision => ENV['REVISION'], :scm_repository => ENV['REPO'], - :local_username => ENV['USER']) + :local_username => ENV['USER'], + :api_key => ENV['API_KEY']) + end + + task :log_stdout do + require 'logger' + RAILS_DEFAULT_LOGGER = Logger.new(STDOUT) end desc "Verify your plugin installation by sending a test exception to the hoptoad service" - task :test => :environment do + task :test => ['hoptoad:log_stdout', :environment] do + RAILS_DEFAULT_LOGGER.level = Logger::DEBUG + require 'action_controller/test_process' + require 'app/controllers/application' if File.exists?('app/controllers/application.rb') request = ActionController::TestRequest.new - response = ActionController::TestResponse.new class HoptoadTestingException < RuntimeError; end - unless HoptoadNotifier.api_key + unless HoptoadNotifier.configuration.api_key puts "Hoptoad needs an API key configured! Check the README to see how to add it." exit end + HoptoadNotifier.configuration.development_environments = [] + in_controller = ApplicationController.included_modules.include? HoptoadNotifier::Catcher in_base = ActionController::Base.included_modules.include? HoptoadNotifier::Catcher if !in_controller || !in_base @@ -30,6 +40,11 @@ namespace :hoptoad do exit end + puts "Configuration:" + HoptoadNotifier.configuration.to_hash.each do |key, value| + puts sprintf("%25s: %s", key.to_s, value.inspect.slice(0, 55)) + end + puts 'Setting up the Controller.' class ApplicationController # This is to bypass any filters that may prevent access to the action. @@ -43,19 +58,27 @@ namespace :hoptoad do rescue_action_in_public exception end - def public_environment? - true - end - # Ensure we actually have an action to go to. def verify; end + def consider_all_requests_local + false + end + + def local_request? + false + end + def exception_class exception_name = ENV['EXCEPTION'] || "HoptoadTestingException" Object.const_get(exception_name) rescue Object.const_set(exception_name, Class.new(Exception)) end + + def logger + nil + end end puts 'Processing request.' diff --git a/vendor/plugins/hoptoad_notifier/test/backtrace_test.rb b/vendor/plugins/hoptoad_notifier/test/backtrace_test.rb new file mode 100644 index 0000000..a31f510 --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/test/backtrace_test.rb @@ -0,0 +1,94 @@ +require File.dirname(__FILE__) + '/helper' + +class BacktraceTest < Test::Unit::TestCase + + should "parse a backtrace into lines" do + array = [ + "app/models/user.rb:13:in `magic'", + "app/controllers/users_controller.rb:8:in `index'" + ] + + backtrace = HoptoadNotifier::Backtrace.parse(array) + + line = backtrace.lines.first + assert_equal '13', line.number + assert_equal 'app/models/user.rb', line.file + assert_equal 'magic', line.method + + line = backtrace.lines.last + assert_equal '8', line.number + assert_equal 'app/controllers/users_controller.rb', line.file + assert_equal 'index', line.method + end + + should "be equal with equal lines" do + one = build_backtrace_array + two = one.dup + assert_equal one, two + + assert_equal HoptoadNotifier::Backtrace.parse(one), HoptoadNotifier::Backtrace.parse(two) + end + + should "parse massive one-line exceptions into multiple lines" do + original_backtrace = HoptoadNotifier::Backtrace. + parse(["one:1:in `one'\n two:2:in `two'\n three:3:in `three`"]) + expected_backtrace = HoptoadNotifier::Backtrace. + parse(["one:1:in `one'", "two:2:in `two'", "three:3:in `three`"]) + + assert_equal expected_backtrace, original_backtrace + end + + context "with a project root" do + setup do + @project_root = '/some/path' + HoptoadNotifier.configure {|config| config.project_root = @project_root } + end + + teardown do + reset_config + end + + should "filter out the project root" do + backtrace_with_root = HoptoadNotifier::Backtrace.parse( + ["#{@project_root}/app/models/user.rb:7:in `latest'", + "#{@project_root}/app/controllers/users_controller.rb:13:in `index'", + "/lib/something.rb:41:in `open'"], + :filters => default_filters) + backtrace_without_root = HoptoadNotifier::Backtrace.parse( + ["[PROJECT_ROOT]/app/models/user.rb:7:in `latest'", + "[PROJECT_ROOT]/app/controllers/users_controller.rb:13:in `index'", + "/lib/something.rb:41:in `open'"]) + + assert_equal backtrace_without_root, backtrace_with_root + end + end + + should "remove notifier trace" do + inside_notifier = ['lib/hoptoad_notifier.rb:13:in `voodoo`'] + outside_notifier = ['users_controller:8:in `index`'] + + without_inside = HoptoadNotifier::Backtrace.parse(outside_notifier) + with_inside = HoptoadNotifier::Backtrace.parse(inside_notifier + outside_notifier, + :filters => default_filters) + + assert_equal without_inside, with_inside + end + + should "run filters on the backtrace" do + filters = [lambda { |line| line.sub('foo', 'bar') }] + input = HoptoadNotifier::Backtrace.parse(["foo:13:in `one'", "baz:14:in `two'"], + :filters => filters) + expected = HoptoadNotifier::Backtrace.parse(["bar:13:in `one'", "baz:14:in `two'"]) + assert_equal expected, input + end + + def build_backtrace_array + ["app/models/user.rb:13:in `magic'", + "app/controllers/users_controller.rb:8:in `index'"] + end + + def default_filters + HoptoadNotifier::Configuration::DEFAULT_BACKTRACE_FILTERS + end + +end diff --git a/vendor/plugins/hoptoad_notifier/test/catcher_test.rb b/vendor/plugins/hoptoad_notifier/test/catcher_test.rb new file mode 100644 index 0000000..0905f27 --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/test/catcher_test.rb @@ -0,0 +1,314 @@ +require File.dirname(__FILE__) + '/helper' + +class CatcherTest < Test::Unit::TestCase + + include DefinesConstants + + def setup + super + reset_config + HoptoadNotifier.sender = CollectingSender.new + define_constant('RAILS_ROOT', '/path/to/rails/root') + end + + def ignore(exception_class) + HoptoadNotifier.configuration.ignore << exception_class + end + + def build_controller_class(&definition) + returning Class.new(ActionController::Base) do |klass| + klass.__send__(:include, HoptoadNotifier::Catcher) + klass.class_eval(&definition) if definition + define_constant('HoptoadTestController', klass) + end + end + + def assert_sent_hash(hash, xpath) + hash.each do |key, value| + element_xpath = "#{xpath}/var[@key = '#{key}']" + if value.respond_to?(:to_hash) + assert_sent_hash value.to_hash, element_xpath + else + assert_sent_element value.to_s, element_xpath + end + end + end + + def assert_sent_element(value, xpath) + assert_valid_node last_sent_notice_document, xpath, value + end + + def assert_sent_request_info_for(request) + params = request.parameters.to_hash + assert_sent_hash params, '/notice/request/params' + assert_sent_element params['controller'], '/notice/request/component' + assert_sent_element params['action'], '/notice/request/action' + assert_sent_element url_from_request(request), '/notice/request/url' + assert_sent_hash request.env, '/notice/request/cgi-data' + end + + def url_from_request(request) + url = "#{request.protocol}#{request.host}" + + unless [80, 443].include?(request.port) + url << ":#{request.port}" + end + + url << request.request_uri + url + end + + def sender + HoptoadNotifier.sender + end + + def last_sent_notice_xml + sender.collected.last + end + + def last_sent_notice_document + assert_not_nil xml = last_sent_notice_xml, "No xml was sent" + Nokogiri::XML.parse(xml) + end + + def process_action(opts = {}, &action) + opts[:request] ||= ActionController::TestRequest.new + opts[:response] ||= ActionController::TestResponse.new + klass = build_controller_class do + cattr_accessor :local + define_method(:index, &action) + def local_request? + local + end + end + if opts[:filters] + klass.filter_parameter_logging *opts[:filters] + end + if opts[:user_agent] + if opts[:request].respond_to?(:user_agent=) + opts[:request].user_agent = opts[:user_agent] + else + opts[:request].env["HTTP_USER_AGENT"] = opts[:user_agent] + end + end + if opts[:port] + opts[:request].port = opts[:port] + end + klass.consider_all_requests_local = opts[:all_local] + klass.local = opts[:local] + controller = klass.new + controller.stubs(:rescue_action_in_public_without_hoptoad) + opts[:request].query_parameters = opts[:request].query_parameters.merge(opts[:params] || {}) + opts[:request].session = ActionController::TestSession.new(opts[:session] || {}) + controller.process(opts[:request], opts[:response]) + controller + end + + def process_action_with_manual_notification(args = {}) + process_action(args) do + notify_hoptoad(:error_message => 'fail') + # Rails will raise a template error if we don't render something + render :nothing => true + end + end + + def process_action_with_automatic_notification(args = {}) + process_action(args) { raise "Hello" } + end + + should "deliver notices from exceptions raised in public requests" do + process_action_with_automatic_notification + assert_caught_and_sent + end + + should "not deliver notices from exceptions in local requests" do + process_action_with_automatic_notification(:local => true) + assert_caught_and_not_sent + end + + should "not deliver notices from exceptions when all requests are local" do + process_action_with_automatic_notification(:all_local => true) + assert_caught_and_not_sent + end + + should "not deliver notices from actions that don't raise" do + controller = process_action { render :text => 'Hello' } + assert_caught_and_not_sent + assert_equal 'Hello', controller.response.body + end + + should "not deliver ignored exceptions raised by actions" do + ignore(RuntimeError) + process_action_with_automatic_notification + assert_caught_and_not_sent + end + + should "deliver ignored exception raised manually" do + ignore(RuntimeError) + process_action_with_manual_notification + assert_caught_and_sent + end + + should "deliver manually sent notices in public requests" do + process_action_with_manual_notification + assert_caught_and_sent + end + + should "not deliver manually sent notices in local requests" do + process_action_with_manual_notification(:local => true) + assert_caught_and_not_sent + end + + should "not deliver manually sent notices when all requests are local" do + process_action_with_manual_notification(:all_local => true) + assert_caught_and_not_sent + end + + should "continue with default behavior after delivering an exception" do + controller = process_action_with_automatic_notification(:public => true) + # TODO: can we test this without stubbing? + assert_received(controller, :rescue_action_in_public_without_hoptoad) + end + + should "not create actions from Hoptoad methods" do + controller = build_controller_class.new + assert_equal [], HoptoadNotifier::Catcher.instance_methods + end + + should "ignore exceptions when user agent is being ignored by regular expression" do + HoptoadNotifier.configuration.ignore_user_agent_only = [/Ignored/] + process_action_with_automatic_notification(:user_agent => 'ShouldBeIgnored') + assert_caught_and_not_sent + end + + should "ignore exceptions when user agent is being ignored by string" do + HoptoadNotifier.configuration.ignore_user_agent_only = ['IgnoredUserAgent'] + process_action_with_automatic_notification(:user_agent => 'IgnoredUserAgent') + assert_caught_and_not_sent + end + + should "not ignore exceptions when user agent is not being ignored" do + HoptoadNotifier.configuration.ignore_user_agent_only = ['IgnoredUserAgent'] + process_action_with_automatic_notification(:user_agent => 'NonIgnoredAgent') + assert_caught_and_sent + end + + should "send session data for manual notifications" do + data = { 'one' => 'two' } + process_action_with_manual_notification(:session => data) + assert_sent_hash data, "/notice/request/session" + end + + should "send session data for automatic notification" do + data = { 'one' => 'two' } + process_action_with_automatic_notification(:session => data) + assert_sent_hash data, "/notice/request/session" + end + + should "send request data for manual notification" do + params = { 'controller' => "hoptoad_test", 'action' => "index" } + controller = process_action_with_manual_notification(:params => params) + assert_sent_request_info_for controller.request + end + + should "send request data for manual notification with non-standard port" do + params = { 'controller' => "hoptoad_test", 'action' => "index" } + controller = process_action_with_manual_notification(:params => params, :port => 81) + assert_sent_request_info_for controller.request + end + + should "send request data for automatic notification" do + params = { 'controller' => "hoptoad_test", 'action' => "index" } + controller = process_action_with_automatic_notification(:params => params) + assert_sent_request_info_for controller.request + end + + should "send request data for automatic notification with non-standard port" do + params = { 'controller' => "hoptoad_test", 'action' => "index" } + controller = process_action_with_automatic_notification(:params => params, :port => 81) + assert_sent_request_info_for controller.request + end + + should "use standard rails logging filters on params and env" do + filtered_params = { "abc" => "123", + "def" => "456", + "ghi" => "[FILTERED]" } + ENV['ghi'] = 'abc' + filtered_env = { 'ghi' => '[FILTERED]' } + filtered_cgi = { 'REQUEST_METHOD' => '[FILTERED]' } + + process_action_with_automatic_notification(:filters => [:ghi, :request_method], + :params => { "abc" => "123", + "def" => "456", + "ghi" => "789" }) + assert_sent_hash filtered_params, '/notice/request/params' + assert_sent_hash filtered_cgi, '/notice/request/cgi-data' + end + + context "for a local error with development lookup enabled" do + setup do + HoptoadNotifier.configuration.development_lookup = true + HoptoadNotifier.stubs(:build_lookup_hash_for).returns({ :awesome => 2 }) + + @controller = process_action_with_automatic_notification(:local => true) + @response = @controller.response + end + + should "append custom CSS and JS to response body for a local error" do + assert_match /text\/css/, @response.body + assert_match /text\/javascript/, @response.body + end + + should "contain host, API key and notice JSON" do + assert_match HoptoadNotifier.configuration.host.to_json, @response.body + assert_match HoptoadNotifier.configuration.api_key.to_json, @response.body + assert_match ({ :awesome => 2 }).to_json, @response.body + end + end + + context "for a local error with development lookup disabled" do + setup do + HoptoadNotifier.configuration.development_lookup = false + + @controller = process_action_with_automatic_notification(:local => true) + @response = @controller.response + end + + should "not append custom CSS and JS to response for a local error" do + assert_no_match /text\/css/, @response.body + assert_no_match /text\/javascript/, @response.body + end + end + + should "call session.to_hash if available" do + hash_data = {:key => :value} + + session = ActionController::TestSession.new + ActionController::TestSession.stubs(:new).returns(session) + session.stubs(:to_hash).returns(hash_data) + + process_action_with_automatic_notification + assert_received(session, :to_hash) + assert_received(session, :data) { |expect| expect.never } + assert_caught_and_sent + end + + should "call session.data if session.to_hash is undefined" do + hash_data = {:key => :value} + + session = ActionController::TestSession.new + ActionController::TestSession.stubs(:new).returns(session) + session.stubs(:data).returns(hash_data) + if session.respond_to?(:to_hash) + class << session + undef to_hash + end + end + + process_action_with_automatic_notification + assert_received(session, :to_hash) { |expect| expect.never } + assert_received(session, :data) { |expect| expect.at_least_once } + assert_caught_and_sent + end + +end diff --git a/vendor/plugins/hoptoad_notifier/test/configuration_test.rb b/vendor/plugins/hoptoad_notifier/test/configuration_test.rb index c9c82e5..27e87d8 100644 --- a/vendor/plugins/hoptoad_notifier/test/configuration_test.rb +++ b/vendor/plugins/hoptoad_notifier/test/configuration_test.rb @@ -1,145 +1,199 @@ require File.dirname(__FILE__) + '/helper' -class ConfigurationTest < ActiveSupport::TestCase - context "HoptoadNotifier configuration" do - setup do - @controller = HoptoadController.new - class ::HoptoadController - include HoptoadNotifier::Catcher - def rescue_action e - rescue_action_in_public e - end - end - assert @controller.methods.include?("notify_hoptoad") - end +class ConfigurationTest < Test::Unit::TestCase + + include DefinesConstants + + should "provide default values" do + assert_config_default :proxy_host, nil + assert_config_default :proxy_port, nil + assert_config_default :proxy_user, nil + assert_config_default :proxy_pass, nil + assert_config_default :project_root, nil + assert_config_default :environment_name, nil + assert_config_default :notifier_version, HoptoadNotifier::VERSION + assert_config_default :notifier_name, 'Hoptoad Notifier' + assert_config_default :notifier_url, 'http://hoptoadapp.com' + assert_config_default :secure, false + assert_config_default :host, 'hoptoadapp.com' + assert_config_default :http_open_timeout, 2 + assert_config_default :http_read_timeout, 5 + assert_config_default :ignore_by_filters, [] + assert_config_default :ignore_user_agent, [] + assert_config_default :params_filters, + HoptoadNotifier::Configuration::DEFAULT_PARAMS_FILTERS + assert_config_default :backtrace_filters, + HoptoadNotifier::Configuration::DEFAULT_BACKTRACE_FILTERS + assert_config_default :ignore, + HoptoadNotifier::Configuration::IGNORE_DEFAULT + assert_config_default :development_lookup, true + end - should "be done with a block" do - HoptoadNotifier.configure do |config| - config.host = "host" - config.port = 3333 - config.secure = true - config.api_key = "1234567890abcdef" - config.ignore << [ RuntimeError ] - config.ignore_user_agent << 'UserAgentString' - config.ignore_user_agent << /UserAgentRegexp/ - config.proxy_host = 'proxyhost1' - config.proxy_port = '80' - config.proxy_user = 'user' - config.proxy_pass = 'secret' - config.http_open_timeout = 2 - config.http_read_timeout = 5 - end - - assert_equal "host", HoptoadNotifier.host - assert_equal 3333, HoptoadNotifier.port - assert_equal true, HoptoadNotifier.secure - assert_equal "1234567890abcdef", HoptoadNotifier.api_key - assert_equal 'proxyhost1', HoptoadNotifier.proxy_host - assert_equal '80', HoptoadNotifier.proxy_port - assert_equal 'user', HoptoadNotifier.proxy_user - assert_equal 'secret', HoptoadNotifier.proxy_pass - assert_equal 2, HoptoadNotifier.http_open_timeout - assert_equal 5, HoptoadNotifier.http_read_timeout - assert_equal HoptoadNotifier::IGNORE_USER_AGENT_DEFAULT + ['UserAgentString', /UserAgentRegexp/], - HoptoadNotifier.ignore_user_agent - assert_equal HoptoadNotifier::IGNORE_DEFAULT + [RuntimeError], - HoptoadNotifier.ignore - end + should "provide default values for secure connections" do + config = HoptoadNotifier::Configuration.new + config.secure = true + assert_equal 443, config.port + assert_equal 'https', config.protocol + end - should "set a default host" do - HoptoadNotifier.instance_variable_set("@host",nil) - assert_equal "hoptoadapp.com", HoptoadNotifier.host - end + should "provide default values for insecure connections" do + config = HoptoadNotifier::Configuration.new + config.secure = false + assert_equal 80, config.port + assert_equal 'http', config.protocol + end - [File.open(__FILE__), Proc.new { puts "boo!" }, Module.new].each do |object| - should "remove #{object.class} when cleaning environment" do - HoptoadNotifier.configure {} - notice = @controller.send(:normalize_notice, {}) - notice[:environment][:strange_object] = object + should "not cache inferred ports" do + config = HoptoadNotifier::Configuration.new + config.secure = false + config.port + config.secure = true + assert_equal 443, config.port + end - assert_nil @controller.send(:clean_non_serializable_data, notice)[:environment][:strange_object] - end - end + should "allow values to be overwritten" do + assert_config_overridable :proxy_host + assert_config_overridable :proxy_port + assert_config_overridable :proxy_user + assert_config_overridable :proxy_pass + assert_config_overridable :secure + assert_config_overridable :host + assert_config_overridable :port + assert_config_overridable :http_open_timeout + assert_config_overridable :http_read_timeout + assert_config_overridable :project_root + assert_config_overridable :notifier_version + assert_config_overridable :notifier_name + assert_config_overridable :notifier_url + assert_config_overridable :environment_name + assert_config_overridable :development_lookup + end - [123, "string", 123_456_789_123_456_789, [:a, :b], {:a => 1}, HashWithIndifferentAccess.new].each do |object| - should "not remove #{object.class} when cleaning environment" do - HoptoadNotifier.configure {} - notice = @controller.send(:normalize_notice, {}) - notice[:environment][:strange_object] = object + should "have an api key" do + assert_config_overridable :api_key + end - assert_equal object, @controller.send(:clean_non_serializable_data, notice)[:environment][:strange_object] - end + should "act like a hash" do + config = HoptoadNotifier::Configuration.new + hash = config.to_hash + [:api_key, :backtrace_filters, :development_environments, + :environment_name, :host, :http_open_timeout, + :http_read_timeout, :ignore, :ignore_by_filters, :ignore_user_agent, + :notifier_name, :notifier_url, :notifier_version, :params_filters, + :project_root, :port, :protocol, :proxy_host, :proxy_pass, :proxy_port, + :proxy_user, :secure, :development_lookup].each do |option| + assert_equal config[option], hash[option], "Wrong value for #{option}" end + end - should "remove notifier trace when cleaning backtrace" do - HoptoadNotifier.configure {} - notice = @controller.send(:normalize_notice, {}) + should "be mergable" do + config = HoptoadNotifier::Configuration.new + hash = config.to_hash + assert_equal hash.merge(:key => 'value'), config.merge(:key => 'value') + end - assert notice[:backtrace].grep(%r{lib/hoptoad_notifier.rb}).any?, notice[:backtrace].inspect + should "allow param filters to be appended" do + assert_appends_value :params_filters + end - dirty_backtrace = @controller.send(:clean_hoptoad_backtrace, notice[:backtrace]) - dirty_backtrace.each do |line| - assert_no_match %r{lib/hoptoad_notifier.rb}, line - end - end + should "warn when attempting to read environment filters" do + config = HoptoadNotifier::Configuration.new + config. + expects(:warn). + with(regexp_matches(/deprecated/i)) + assert_equal [], config.environment_filters + end - should "add filters to the backtrace_filters" do - assert_difference "HoptoadNotifier.backtrace_filters.length", 5 do - HoptoadNotifier.configure do |config| - config.filter_backtrace do |line| - line = "1234" - end - end - end + should "allow ignored user agents to be appended" do + assert_appends_value :ignore_user_agent + end - assert_equal %w( 1234 1234 ), @controller.send(:clean_hoptoad_backtrace, %w( foo bar )) + should "allow backtrace filters to be appended" do + assert_appends_value(:backtrace_filters) do |config| + new_filter = lambda {} + config.filter_backtrace(&new_filter) + new_filter end + end - should "use standard rails logging filters on params and env" do - ::HoptoadController.class_eval do - filter_parameter_logging :ghi - end - - expected = {"notice" => {"request" => {"params" => {"abc" => "123", "def" => "456", "ghi" => "[FILTERED]"}}, - "environment" => {"abc" => "123", "ghi" => "[FILTERED]"}}} - notice = {"notice" => {"request" => {"params" => {"abc" => "123", "def" => "456", "ghi" => "789"}}, - "environment" => {"abc" => "123", "ghi" => "789"}}} - assert @controller.respond_to?(:filter_parameters) - assert_equal( expected[:notice], @controller.send(:clean_notice, notice)[:notice] ) + should "allow ignore by filters to be appended" do + assert_appends_value(:ignore_by_filters) do |config| + new_filter = lambda {} + config.ignore_by_filter(&new_filter) + new_filter end + end - should "add filters to the params filters" do - assert_difference "HoptoadNotifier.params_filters.length", 2 do - HoptoadNotifier.configure do |config| - config.params_filters << "abc" - config.params_filters << "def" - end - end + should "allow ignored exceptions to be appended" do + config = HoptoadNotifier::Configuration.new + original_filters = config.ignore.dup + new_filter = 'hello' + config.ignore << new_filter + assert_same_elements original_filters + [new_filter], config.ignore + end - assert HoptoadNotifier.params_filters.include?( "abc" ) - assert HoptoadNotifier.params_filters.include?( "def" ) + should "allow ignored exceptions to be replaced" do + assert_replaces(:ignore, :ignore_only=) + end - assert_equal( {:abc => "[FILTERED]", :def => "[FILTERED]", :ghi => "789"}, - @controller.send(:clean_hoptoad_params, :abc => "123", :def => "456", :ghi => "789" ) ) - end + should "allow ignored user agents to be replaced" do + assert_replaces(:ignore_user_agent, :ignore_user_agent_only=) + end - should "add filters to the environment filters" do - assert_difference "HoptoadNotifier.environment_filters.length", 2 do - HoptoadNotifier.configure do |config| - config.environment_filters << "secret" - config.environment_filters << "supersecret" - end - end + should "use development and test as development environments by default" do + config = HoptoadNotifier::Configuration.new + assert_same_elements %w(development test cucumber), config.development_environments + end - assert HoptoadNotifier.environment_filters.include?( "secret" ) - assert HoptoadNotifier.environment_filters.include?( "supersecret" ) + should "be public in a public environment" do + config = HoptoadNotifier::Configuration.new + config.development_environments = %w(development) + config.environment_name = 'production' + assert config.public? + end - assert_equal( {:secret => "[FILTERED]", :supersecret => "[FILTERED]", :ghi => "789"}, - @controller.send(:clean_hoptoad_environment, :secret => "123", :supersecret => "456", :ghi => "789" ) ) - end + should "not be public in a development environment" do + config = HoptoadNotifier::Configuration.new + config.development_environments = %w(staging) + config.environment_name = 'staging' + assert !config.public? + end - should "have at default ignored exceptions" do - assert HoptoadNotifier::IGNORE_DEFAULT.any? + should "be public without an environment name" do + config = HoptoadNotifier::Configuration.new + assert config.public? + end + + def assert_config_default(option, default_value, config = nil) + config ||= HoptoadNotifier::Configuration.new + assert_equal default_value, config.send(option) + end + + def assert_config_overridable(option, value = 'a value') + config = HoptoadNotifier::Configuration.new + config.send(:"#{option}=", value) + assert_equal value, config.send(option) + end + + def assert_appends_value(option, &block) + config = HoptoadNotifier::Configuration.new + original_values = config.send(option).dup + block ||= lambda do |config| + new_value = 'hello' + config.send(option) << new_value + new_value end + new_value = block.call(config) + assert_same_elements original_values + [new_value], config.send(option) + end + + def assert_replaces(option, setter) + config = HoptoadNotifier::Configuration.new + new_value = 'hello' + config.send(setter, [new_value]) + assert_equal [new_value], config.send(option) + config.send(setter, new_value) + assert_equal [new_value], config.send(option) end + end diff --git a/vendor/plugins/hoptoad_notifier/test/controller_test.rb b/vendor/plugins/hoptoad_notifier/test/controller_test.rb deleted file mode 100644 index bd4f504..0000000 --- a/vendor/plugins/hoptoad_notifier/test/controller_test.rb +++ /dev/null @@ -1,366 +0,0 @@ -require File.dirname(__FILE__) + '/helper' - -def expect_session_data_for(controller) - # NOTE: setting expectations on the controller is not a good idea here, - # because the controller is the unit we're trying to test. However, as all - # exception-related behavior is mixed into the controller itsef, we have - # little choice. Delegating notifier methods from the controller to a - # Sender could make this easier to maintain and test. - - @controller.expects(:send_to_hoptoad).with do |params| - assert params.respond_to?(:to_hash), "The notifier needs a hash" - notice = params[:notice] - assert_not_nil notice, "No notice passed to the notifier" - assert_not_nil notice[:session][:key], "No session key was set" - assert_not_nil notice[:session][:data], "No session data was set" - true - end - @controller.stubs(:rescue_action_in_public_without_hoptoad) -end - -def should_notify_normally - should "have inserted its methods into the controller" do - assert @controller.methods.include?("inform_hoptoad") - end - - should "prevent raises and send the error to hoptoad" do - @controller.expects(:notify_hoptoad) - @controller.expects(:rescue_action_in_public_without_hoptoad) - assert_nothing_raised do - request("do_raise") - end - end - - should "allow a non-raising action to complete" do - assert_nothing_raised do - request("do_not_raise") - end - end - - should "allow manual sending of exceptions" do - @controller.expects(:notify_hoptoad) - @controller.expects(:rescue_action_in_public_without_hoptoad).never - assert_nothing_raised do - request("manual_notify") - end - end - - should "disable manual sending of exceptions in a non-public (development or test) environment" do - @controller.stubs(:public_environment?).returns(false) - @controller.expects(:send_to_hoptoad).never - @controller.expects(:rescue_action_in_public_without_hoptoad).never - assert_nothing_raised do - request("manual_notify") - end - end - - should "send even ignored exceptions if told manually" do - @controller.expects(:notify_hoptoad) - @controller.expects(:rescue_action_in_public_without_hoptoad).never - assert_nothing_raised do - request("manual_notify_ignored") - end - end - - should "ignore default exceptions" do - @controller.expects(:notify_hoptoad).never - @controller.expects(:rescue_action_in_public_without_hoptoad) - assert_nothing_raised do - request("do_raise_ignored") - end - end - - should "filter non-serializable data" do - File.open(__FILE__) do |file| - assert_equal( {:ghi => "789"}, - @controller.send(:clean_non_serializable_data, :ghi => "789", :class => Class.new, :file => file) ) - end - end - - should "apply all params, environment and technical filters" do - params_hash = {:abc => 123} - environment_hash = {:def => 456} - backtrace_data = :backtrace_data - - raw_notice = {:request => {:params => params_hash}, - :environment => environment_hash, - :backtrace => backtrace_data} - - processed_notice = {:backtrace => :backtrace_data, - :request => {:params => :params_data}, - :environment => :environment_data} - - @controller.expects(:clean_hoptoad_backtrace).with(backtrace_data).returns(:backtrace_data) - @controller.expects(:clean_hoptoad_params).with(params_hash).returns(:params_data) - @controller.expects(:clean_hoptoad_environment).with(environment_hash).returns(:environment_data) - @controller.expects(:clean_non_serializable_data).with(processed_notice).returns(:serializable_data) - - assert_equal(:serializable_data, @controller.send(:clean_notice, raw_notice)) - end - - should "send session data to hoptoad when the session has @data" do - expect_session_data_for(@controller) - @request = ActionController::TestRequest.new - @request.action = 'do_raise' - @request.session.instance_variable_set("@data", { :message => 'Hello' }) - @response = ActionController::TestResponse.new - @controller.process(@request, @response) - end - - should "send session data to hoptoad when the session responds to to_hash" do - expect_session_data_for(@controller) - @request = ActionController::TestRequest.new - @request.action = 'do_raise' - @request.session.stubs(:to_hash).returns(:message => 'Hello') - @response = ActionController::TestResponse.new - @controller.process(@request, @response) - end -end - -def should_auto_include_catcher - should "auto-include for ApplicationController" do - assert ApplicationController.include?(HoptoadNotifier::Catcher) - end -end - -class ControllerTest < ActiveSupport::TestCase - context "Hoptoad inclusion" do - should "be able to occur even outside Rails controllers" do - assert_nothing_raised do - class MyHoptoad - include HoptoadNotifier::Catcher - end - end - my = MyHoptoad.new - assert my.respond_to?(:notify_hoptoad) - end - - context "when auto-included" do - setup do - class ::ApplicationController < ActionController::Base - end - - class ::AutoIncludeController < ::ApplicationController - include TestMethods - def rescue_action e - rescue_action_in_public e - end - end - - HoptoadNotifier.ignore_only = HoptoadNotifier::IGNORE_DEFAULT - @controller = ::AutoIncludeController.new - @controller.stubs(:public_environment?).returns(true) - @controller.stubs(:send_to_hoptoad) - - HoptoadNotifier.configure do |config| - config.api_key = "1234567890abcdef" - end - end - - context "when included through the configure block" do - should_auto_include_catcher - should_notify_normally - end - - context "when included both through configure and normally" do - setup do - class ::AutoIncludeController < ::ApplicationController - include HoptoadNotifier::Catcher - end - end - should_auto_include_catcher - should_notify_normally - end - end - end - - context "when the logger is overridden for an action" do - setup do - class ::IgnoreActionController < ::ActionController::Base - include TestMethods - include HoptoadNotifier::Catcher - def rescue_action e - rescue_action_in_public e - end - def logger - super unless action_name == "do_raise" - end - end - ::ActionController::Base.logger = Logger.new(STDOUT) - @controller = ::IgnoreActionController.new - @controller.stubs(:public_environment?).returns(true) - @controller.stubs(:rescue_action_in_public_without_hoptoad) - - # stubbing out Net::HTTP as well - @body = 'body' - @http = stub(:post => @response, :read_timeout= => nil, :open_timeout= => nil, :use_ssl= => nil) - Net::HTTP.stubs(:new).returns(@http) - HoptoadNotifier.port = nil - HoptoadNotifier.host = nil - HoptoadNotifier.proxy_host = nil - end - - should "work when action is called and request works" do - @response = stub(:body => @body, :class => Net::HTTPSuccess) - assert_nothing_raised do - request("do_raise") - end - end - - should "work when action is called and request doesn't work" do - @response = stub(:body => @body, :class => Net::HTTPError) - assert_nothing_raised do - request("do_raise") - end - end - - should "work when action is called and hoptoad times out" do - @http.stubs(:post).raises(TimeoutError) - assert_nothing_raised do - request("do_raise") - end - end - end - - context "The hoptoad test controller" do - setup do - @controller = ::HoptoadController.new - class ::HoptoadController - def rescue_action e - raise e - end - end - end - - context "with no notifier catcher" do - should "not prevent raises" do - assert_raises RuntimeError do - request("do_raise") - end - end - - should "allow a non-raising action to complete" do - assert_nothing_raised do - request("do_not_raise") - end - end - end - - context "with the notifier installed" do - setup do - class ::HoptoadController - include HoptoadNotifier::Catcher - def rescue_action e - rescue_action_in_public e - end - end - HoptoadNotifier.ignore_only = HoptoadNotifier::IGNORE_DEFAULT - @controller.stubs(:public_environment?).returns(true) - @controller.stubs(:send_to_hoptoad) - end - - should_notify_normally - - context "and configured to ignore additional exceptions" do - setup do - HoptoadNotifier.ignore << ActiveRecord::StatementInvalid - end - - should "still ignore default exceptions" do - @controller.expects(:notify_hoptoad).never - @controller.expects(:rescue_action_in_public_without_hoptoad) - assert_nothing_raised do - request("do_raise_ignored") - end - end - - should "ignore specified exceptions" do - @controller.expects(:notify_hoptoad).never - @controller.expects(:rescue_action_in_public_without_hoptoad) - assert_nothing_raised do - request("do_raise_not_ignored") - end - end - - should "not ignore unspecified, non-default exceptions" do - @controller.expects(:notify_hoptoad) - @controller.expects(:rescue_action_in_public_without_hoptoad) - assert_nothing_raised do - request("do_raise") - end - end - end - - context "and configured to ignore only certain exceptions" do - setup do - HoptoadNotifier.ignore_only = [ActiveRecord::StatementInvalid] - end - - should "no longer ignore default exceptions" do - @controller.expects(:notify_hoptoad) - @controller.expects(:rescue_action_in_public_without_hoptoad) - assert_nothing_raised do - request("do_raise_ignored") - end - end - - should "ignore specified exceptions" do - @controller.expects(:notify_hoptoad).never - @controller.expects(:rescue_action_in_public_without_hoptoad) - assert_nothing_raised do - request("do_raise_not_ignored") - end - end - - should "not ignore unspecified, non-default exceptions" do - @controller.expects(:notify_hoptoad) - @controller.expects(:rescue_action_in_public_without_hoptoad) - assert_nothing_raised do - request("do_raise") - end - end - end - - context "and configured to ignore certain user agents" do - setup do - HoptoadNotifier.ignore_user_agent << /Ignored/ - HoptoadNotifier.ignore_user_agent << 'IgnoredUserAgent' - end - - should "ignore exceptions when user agent is being ignored" do - @controller.expects(:notify_hoptoad).never - @controller.expects(:rescue_action_in_public_without_hoptoad) - assert_nothing_raised do - request("do_raise", :get, 'IgnoredUserAgent') - end - end - - should "ignore exceptions when user agent is being ignored (regexp)" do - HoptoadNotifier.ignore_user_agent_only = [/Ignored/] - @controller.expects(:notify_hoptoad).never - @controller.expects(:rescue_action_in_public_without_hoptoad) - assert_nothing_raised do - request("do_raise", :get, 'IgnoredUserAgent') - end - end - - should "ignore exceptions when user agent is being ignored (string)" do - HoptoadNotifier.ignore_user_agent_only = ['IgnoredUserAgent'] - @controller.expects(:notify_hoptoad).never - @controller.expects(:rescue_action_in_public_without_hoptoad) - assert_nothing_raised do - request("do_raise", :get, 'IgnoredUserAgent') - end - end - - should "not ignore exceptions when user agent is not being ignored" do - @controller.expects(:notify_hoptoad) - @controller.expects(:rescue_action_in_public_without_hoptoad) - assert_nothing_raised do - request("do_raise") - end - end - end - end - end -end diff --git a/vendor/plugins/hoptoad_notifier/test/helper.rb b/vendor/plugins/hoptoad_notifier/test/helper.rb index 954fbaf..02dd994 100644 --- a/vendor/plugins/hoptoad_notifier/test/helper.rb +++ b/vendor/plugins/hoptoad_notifier/test/helper.rb @@ -1,8 +1,10 @@ require 'test/unit' require 'rubygems' -require 'mocha' -gem 'thoughtbot-shoulda', ">= 2.0.0" + +gem 'jferris-mocha', '0.9.5.0.1241126838' + require 'shoulda' +require 'mocha' $LOAD_PATH << File.join(File.dirname(__FILE__), *%w[.. vendor ginger lib]) require 'ginger' @@ -12,13 +14,10 @@ require 'action_controller/test_process' require 'active_record' require 'active_record/base' require 'active_support' -require 'active_support/test_case' +require 'nokogiri' require File.join(File.dirname(__FILE__), "..", "lib", "hoptoad_notifier") -RAILS_ROOT = File.join( File.dirname(__FILE__), "rails_root" ) -RAILS_ENV = "test" - begin require 'redgreen'; rescue LoadError; end module TestMethods @@ -57,10 +56,183 @@ class HoptoadController < ActionController::Base include TestMethods end -def request(action = nil, method = :get, user_agent = nil) - @request = ActionController::TestRequest.new - @request.action = action ? action.to_s : "" - @request.user_agent = user_agent unless user_agent.nil? - @response = ActionController::TestResponse.new - @controller.process(@request, @response) +class Test::Unit::TestCase + def request(action = nil, method = :get, user_agent = nil, params = {}) + @request = ActionController::TestRequest.new + @request.action = action ? action.to_s : "" + + if user_agent + if @request.respond_to?(:user_agent=) + @request.user_agent = user_agent + else + @request.env["HTTP_USER_AGENT"] = user_agent + end + end + @request.query_parameters = @request.query_parameters.merge(params) + @response = ActionController::TestResponse.new + @controller.process(@request, @response) + end + + # Borrowed from ActiveSupport 2.3.2 + def assert_difference(expression, difference = 1, message = nil, &block) + b = block.send(:binding) + exps = Array.wrap(expression) + before = exps.map { |e| eval(e, b) } + + yield + + exps.each_with_index do |e, i| + error = "#{e.inspect} didn't change by #{difference}" + error = "#{message}.\n#{error}" if message + assert_equal(before[i] + difference, eval(e, b), error) + end + end + + def assert_no_difference(expression, message = nil, &block) + assert_difference expression, 0, message, &block + end + + def stub_sender + stub('sender', :send_to_hoptoad => nil) + end + + def stub_sender! + HoptoadNotifier.sender = stub_sender + end + + def stub_notice + stub('notice', :to_xml => 'some yaml', :ignore? => false) + end + + def stub_notice! + returning stub_notice do |notice| + HoptoadNotifier::Notice.stubs(:new => notice) + end + end + + def create_dummy + HoptoadNotifier::DummySender.new + end + + def reset_config + HoptoadNotifier.configuration = nil + HoptoadNotifier.configure do |config| + config.api_key = 'abc123' + end + end + + def clear_backtrace_filters + HoptoadNotifier.configuration.backtrace_filters.clear + end + + def build_exception + raise + rescue => caught_exception + caught_exception + end + + def build_notice_data(exception = nil) + exception ||= build_exception + { + :api_key => 'abc123', + :error_class => exception.class.name, + :error_message => "#{exception.class.name}: #{exception.message}", + :backtrace => exception.backtrace, + :environment => { 'PATH' => '/bin', 'REQUEST_URI' => '/users/1' }, + :request => { + :params => { 'controller' => 'users', 'action' => 'show', 'id' => '1' }, + :rails_root => '/path/to/application', + :url => "http://test.host/users/1" + }, + :session => { + :key => '123abc', + :data => { 'user_id' => '5', 'flash' => { 'notice' => 'Logged in successfully' } } + } + } + end + + def assert_caught_and_sent + assert !HoptoadNotifier.sender.collected.empty? + end + + def assert_caught_and_not_sent + assert HoptoadNotifier.sender.collected.empty? + end + + def assert_array_starts_with(expected, actual) + assert_respond_to actual, :to_ary + array = actual.to_ary.reverse + expected.reverse.each_with_index do |value, i| + assert_equal value, array[i] + end + end + + def assert_valid_node(document, xpath, content) + nodes = document.xpath(xpath) + assert nodes.any?{|node| node.content == content }, + "Expected xpath #{xpath} to have content #{content}, " + + "but found #{nodes.map { |n| n.content }} in #{nodes.size} matching nodes." + + "Document:\n#{document.to_s}" + end +end + +module DefinesConstants + def setup + @defined_constants = [] + end + + def teardown + @defined_constants.each do |constant| + Object.__send__(:remove_const, constant) + end + end + + def define_constant(name, value) + Object.const_set(name, value) + @defined_constants << name + end +end + +# Also stolen from AS 2.3.2 +class Array + # Wraps the object in an Array unless it's an Array. Converts the + # object to an Array using #to_ary if it implements that. + def self.wrap(object) + case object + when nil + [] + when self + object + else + if object.respond_to?(:to_ary) + object.to_ary + else + [object] + end + end + end + end + +class CollectingSender + attr_reader :collected + + def initialize + @collected = [] + end + + def send_to_hoptoad(data) + @collected << data + end +end + +class FakeLogger + def info(*args); end + def debug(*args); end + def warn(*args); end + def error(*args); end + def fatal(*args); end +end + +RAILS_DEFAULT_LOGGER = FakeLogger.new + diff --git a/vendor/plugins/hoptoad_notifier/test/hoptoad_2_0.xsd b/vendor/plugins/hoptoad_notifier/test/hoptoad_2_0.xsd new file mode 100644 index 0000000..ed92103 --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/test/hoptoad_2_0.xsd @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/plugins/hoptoad_notifier/test/hoptoad_tasks_test.rb b/vendor/plugins/hoptoad_notifier/test/hoptoad_tasks_test.rb index 01e96ba..7d1290d 100644 --- a/vendor/plugins/hoptoad_notifier/test/hoptoad_tasks_test.rb +++ b/vendor/plugins/hoptoad_notifier/test/hoptoad_tasks_test.rb @@ -6,7 +6,7 @@ require 'fakeweb' FakeWeb.allow_net_connect = false -class HoptoadTasksTest < ActiveSupport::TestCase +class HoptoadTasksTest < Test::Unit::TestCase def successful_response(body = "") response = Net::HTTPSuccess.new('1.2', '200', 'OK') response.stubs(:body).returns(body) @@ -50,7 +50,7 @@ class HoptoadTasksTest < ActiveSupport::TestCase before_should "use the project api key" do Net::HTTP.expects(:post_form). - with(kind_of(URI), has_entries(:api_key => "1234123412341234")). + with(kind_of(URI), has_entries('api_key' => "1234123412341234")). returns(successful_response) end @@ -69,6 +69,13 @@ class HoptoadTasksTest < ActiveSupport::TestCase end end + before_should "use the :api_key param if it's passed in." do + @options[:api_key] = "value" + Net::HTTP.expects(:post_form). + with(kind_of(URI), has_entries("api_key" => "value")). + returns(successful_response) + end + before_should "puts the response body on success" do HoptoadTasks.expects(:puts).with("body") Net::HTTP.expects(:post_form).with(any_parameters).returns(successful_response('body')) diff --git a/vendor/plugins/hoptoad_notifier/test/logger_test.rb b/vendor/plugins/hoptoad_notifier/test/logger_test.rb new file mode 100644 index 0000000..ed50fd4 --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/test/logger_test.rb @@ -0,0 +1,85 @@ +require File.dirname(__FILE__) + '/helper' + +class LoggerTest < Test::Unit::TestCase + def stub_http(response, body = nil) + response.stubs(:body => body) if body + @http = stub(:post => response, + :read_timeout= => nil, + :open_timeout= => nil, + :use_ssl= => nil) + Net::HTTP.stubs(:new).returns(@http) + end + + def send_notice + HoptoadNotifier.sender.send_to_hoptoad('data') + end + + def stub_verbose_log + HoptoadNotifier.stubs(:write_verbose_log) + end + + def assert_logged(expected) + assert_received(HoptoadNotifier, :write_verbose_log) do |expect| + expect.with {|actual| actual =~ expected } + end + end + + def assert_not_logged(expected) + assert_received(HoptoadNotifier, :write_verbose_log) do |expect| + expect.with {|actual| actual =~ expected }.never + end + end + + def configure + HoptoadNotifier.configure { |config| } + end + + should "report that notifier is ready when configured" do + stub_verbose_log + configure + assert_logged /Notifier (.*) ready/ + end + + should "not report that notifier is ready when internally configured" do + stub_verbose_log + HoptoadNotifier.configure(true) { |config | } + assert_not_logged /.*/ + end + + should "print environment info a successful notification without a body" do + reset_config + stub_verbose_log + stub_http(Net::HTTPSuccess) + send_notice + assert_logged /Environment Info:/ + assert_not_logged /Response from Hoptoad:/ + end + + should "print environment info on a failed notification without a body" do + reset_config + stub_verbose_log + stub_http(Net::HTTPError) + send_notice + assert_logged /Environment Info:/ + assert_not_logged /Response from Hoptoad:/ + end + + should "print environment info and response on a success with a body" do + reset_config + stub_verbose_log + stub_http(Net::HTTPSuccess, 'test') + send_notice + assert_logged /Environment Info:/ + assert_logged /Response from Hoptoad:/ + end + + should "print environment info and response on a failure with a body" do + reset_config + stub_verbose_log + stub_http(Net::HTTPError, 'test') + send_notice + assert_logged /Environment Info:/ + assert_logged /Response from Hoptoad:/ + end + +end diff --git a/vendor/plugins/hoptoad_notifier/test/notice_test.rb b/vendor/plugins/hoptoad_notifier/test/notice_test.rb new file mode 100644 index 0000000..59418fd --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/test/notice_test.rb @@ -0,0 +1,363 @@ +require File.dirname(__FILE__) + '/helper' + +class NoticeTest < Test::Unit::TestCase + + include DefinesConstants + + def configure + returning HoptoadNotifier::Configuration.new do |config| + config.api_key = 'abc123def456' + end + end + + def build_notice(args = {}) + configuration = args.delete(:configuration) || configure + HoptoadNotifier::Notice.new(configuration.merge(args)) + end + + def stub_request(attrs = {}) + stub('request', { :parameters => { 'one' => 'two' }, + :protocol => 'http', + :host => 'some.host', + :request_uri => '/some/uri', + :session => { :to_hash => { 'a' => 'b' } }, + :env => { 'three' => 'four' } }.update(attrs)) + end + + should "set the api key" do + api_key = 'key' + notice = build_notice(:api_key => api_key) + assert_equal api_key, notice.api_key + end + + should "accept a project root" do + project_root = '/path/to/project' + notice = build_notice(:project_root => project_root) + assert_equal project_root, notice.project_root + end + + should "accept a component" do + assert_equal 'users_controller', build_notice(:component => 'users_controller').controller + end + + should "alias the component as controller" do + assert_equal 'users_controller', build_notice(:controller => 'users_controller').component + assert_equal 'users_controller', build_notice(:component => 'users_controller').controller + end + + should "accept a action" do + assert_equal 'index', build_notice(:action => 'index').action + end + + should "accept a url" do + url = 'http://some.host/uri' + notice = build_notice(:url => url) + assert_equal url, notice.url + end + + should "accept a backtrace from an exception or hash" do + array = ["user.rb:34:in `crazy'"] + exception = build_exception + exception.set_backtrace array + backtrace = HoptoadNotifier::Backtrace.parse(array) + notice_from_exception = build_notice(:exception => exception) + + + assert_equal notice_from_exception.backtrace, + backtrace, + "backtrace was not correctly set from an exception" + + notice_from_hash = build_notice(:backtrace => array) + assert_equal notice_from_hash.backtrace, + backtrace, + "backtrace was not correctly set from a hash" + end + + should "set the error class from an exception or hash" do + assert_accepts_exception_attribute :error_class do |exception| + exception.class.name + end + end + + should "set the error message from an exception or hash" do + assert_accepts_exception_attribute :error_message do |exception| + "#{exception.class.name}: #{exception.message}" + end + end + + should "accept parameters from a request or hash" do + parameters = { 'one' => 'two' } + notice_from_hash = build_notice(:parameters => parameters) + assert_equal notice_from_hash.parameters, parameters + end + + should "accept session data from a session[:data] hash" do + data = { 'one' => 'two' } + notice = build_notice(:session => { :data => data }) + assert_equal data, notice.session_data + end + + should "accept session data from a session_data hash" do + data = { 'one' => 'two' } + notice = build_notice(:session_data => data) + assert_equal data, notice.session_data + end + + should "accept an environment name" do + assert_equal 'development', build_notice(:environment_name => 'development').environment_name + end + + should "accept CGI data from a hash" do + data = { 'string' => 'value' } + notice = build_notice(:cgi_data => data) + assert_equal data, notice.cgi_data, "should take CGI data from a hash" + end + + should "accept notifier information" do + params = { :notifier_name => 'a name for a notifier', + :notifier_version => '1.0.5', + :notifier_url => 'http://notifiers.r.us/download' } + notice = build_notice(params) + assert_equal params[:notifier_name], notice.notifier_name + assert_equal params[:notifier_version], notice.notifier_version + assert_equal params[:notifier_url], notice.notifier_url + end + + should "set sensible defaults without an exception" do + backtrace = HoptoadNotifier::Backtrace.parse(caller) + notice = build_notice + + assert_equal 'Notification', notice.error_message + assert_array_starts_with backtrace.lines, notice.backtrace.lines + assert_equal({}, notice.parameters) + assert_equal({}, notice.session_data) + end + + should "use the caller as the backtrace for an exception without a backtrace" do + backtrace = HoptoadNotifier::Backtrace.parse(caller) + notice = build_notice(:exception => StandardError.new('error'), :backtrace => nil) + + assert_array_starts_with backtrace.lines, notice.backtrace.lines + end + + should "convert unserializable objects to strings" do + assert_serializes_hash(:parameters) + assert_serializes_hash(:cgi_data) + assert_serializes_hash(:session_data) + end + + should "filter parameters" do + assert_filters_hash(:parameters) + end + + should "filter cgi data" do + assert_filters_hash(:cgi_data) + end + + context "a Notice turned into XML" do + setup do + HoptoadNotifier.configure do |config| + config.api_key = "1234567890" + end + + @exception = build_exception + + @notice = build_notice({ + :notifier_name => 'a name', + :notifier_version => '1.2.3', + :notifier_url => 'http://some.url/path', + :exception => @exception, + :controller => "controller", + :action => "action", + :url => "http://url.com", + :parameters => { "paramskey" => "paramsvalue", + "nestparentkey" => { "nestkey" => "nestvalue" } }, + :session_data => { "sessionkey" => "sessionvalue" }, + :cgi_data => { "cgikey" => "cgivalue" }, + :project_root => "RAILS_ROOT", + :environment_name => "RAILS_ENV" + }) + + @xml = @notice.to_xml + + @document = Nokogiri::XML::Document.parse(@xml) + end + + should "validate against the XML schema" do + assert_valid_notice_document @document + end + + should "serialize a Notice to XML when sent #to_xml" do + assert_valid_node(@document, "//api-key", @notice.api_key) + + assert_valid_node(@document, "//notifier/name", @notice.notifier_name) + assert_valid_node(@document, "//notifier/version", @notice.notifier_version) + assert_valid_node(@document, "//notifier/url", @notice.notifier_url) + + assert_valid_node(@document, "//error/class", @notice.error_class) + assert_valid_node(@document, "//error/message", @notice.error_message) + + assert_valid_node(@document, "//error/backtrace/line/@number", @notice.backtrace.lines.first.number) + assert_valid_node(@document, "//error/backtrace/line/@file", @notice.backtrace.lines.first.file) + assert_valid_node(@document, "//error/backtrace/line/@method", @notice.backtrace.lines.first.method) + + assert_valid_node(@document, "//request/url", @notice.url) + assert_valid_node(@document, "//request/component", @notice.controller) + assert_valid_node(@document, "//request/action", @notice.action) + + assert_valid_node(@document, "//request/params/var/@key", "paramskey") + assert_valid_node(@document, "//request/params/var", "paramsvalue") + assert_valid_node(@document, "//request/params/var/@key", "nestparentkey") + assert_valid_node(@document, "//request/params/var/var/@key", "nestkey") + assert_valid_node(@document, "//request/params/var/var", "nestvalue") + assert_valid_node(@document, "//request/session/var/@key", "sessionkey") + assert_valid_node(@document, "//request/session/var", "sessionvalue") + assert_valid_node(@document, "//request/cgi-data/var/@key", "cgikey") + assert_valid_node(@document, "//request/cgi-data/var", "cgivalue") + + assert_valid_node(@document, "//server-environment/project-root", "RAILS_ROOT") + assert_valid_node(@document, "//server-environment/environment-name", "RAILS_ENV") + end + end + + should "not send empty request data" do + notice = build_notice + assert_nil notice.url + assert_nil notice.controller + assert_nil notice.action + + xml = notice.to_xml + document = Nokogiri::XML.parse(xml) + assert_nil document.at('//request/url') + assert_nil document.at('//request/component') + assert_nil document.at('//request/action') + + assert_valid_notice_document document + end + + %w(url controller action).each do |var| + should "send a request if #{var} is present" do + notice = build_notice(var.to_sym => 'value') + xml = notice.to_xml + document = Nokogiri::XML.parse(xml) + assert_not_nil document.at('//request') + end + end + + %w(parameters cgi_data session_data).each do |var| + should "send a request if #{var} is present" do + notice = build_notice(var.to_sym => { 'key' => 'value' }) + xml = notice.to_xml + document = Nokogiri::XML.parse(xml) + assert_not_nil document.at('//request') + end + end + + should "not ignore an exception not matching ignore filters" do + notice = build_notice(:error_class => 'ArgumentError', + :ignore => ['Argument'], + :ignore_by_filters => [lambda { |notice| false }]) + assert !notice.ignore? + end + + should "ignore an exception with a matching error class" do + notice = build_notice(:error_class => 'ArgumentError', + :ignore => [ArgumentError]) + assert notice.ignore? + end + + should "ignore an exception with a matching error class name" do + notice = build_notice(:error_class => 'ArgumentError', + :ignore => ['ArgumentError']) + assert notice.ignore? + end + + should "ignore an exception with a matching filter" do + filter = lambda {|notice| notice.error_class == 'ArgumentError' } + notice = build_notice(:error_class => 'ArgumentError', + :ignore_by_filters => [filter]) + assert notice.ignore? + end + + should "not raise without an ignore list" do + notice = build_notice(:ignore => nil, :ignore_by_filters => nil) + assert_nothing_raised do + notice.ignore? + end + end + + should "act like a hash" do + notice = build_notice(:error_message => 'some message') + assert_equal notice.error_message, notice[:error_message] + end + + should "return params on notice[:request][:params]" do + params = { 'one' => 'two' } + notice = build_notice(:parameters => params) + assert_equal params, notice[:request][:params] + end + + should "ensure #to_hash is called on objects that support it" do + assert_nothing_raised do + build_notice(:session => { :object => stub(:to_hash => {}) }) + end + end + + def assert_accepts_exception_attribute(attribute, args = {}, &block) + exception = build_exception + block ||= lambda { exception.send(attribute) } + value = block.call(exception) + + notice_from_exception = build_notice(args.merge(:exception => exception)) + + assert_equal notice_from_exception.send(attribute), + value, + "#{attribute} was not correctly set from an exception" + + notice_from_hash = build_notice(args.merge(attribute => value)) + assert_equal notice_from_hash.send(attribute), + value, + "#{attribute} was not correctly set from a hash" + end + + def assert_serializes_hash(attribute) + [File.open(__FILE__), Proc.new { puts "boo!" }, Module.new].each do |object| + hash = { + :strange_object => object, + :sub_hash => { + :sub_object => object + }, + :array => [object] + } + notice = build_notice(attribute => hash) + hash = notice.send(attribute) + assert_equal object.to_s, hash[:strange_object], "objects should be serialized" + assert_kind_of Hash, hash[:sub_hash], "subhashes should be kept" + assert_equal object.to_s, hash[:sub_hash][:sub_object], "subhash members should be serialized" + assert_kind_of Array, hash[:array], "arrays should be kept" + assert_equal object.to_s, hash[:array].first, "array members should be serialized" + end + end + + def assert_valid_notice_document(document) + xsd_path = File.join(File.dirname(__FILE__), "hoptoad_2_0.xsd") + schema = Nokogiri::XML::Schema.new(IO.read(xsd_path)) + errors = schema.validate(document) + assert errors.empty?, errors.collect{|e| e.message }.join + end + + def assert_filters_hash(attribute) + filters = %w(abc def) + original = { 'abc' => "123", 'def' => "456", 'ghi' => "789", 'nested' => { 'abc' => '100' } } + filtered = { 'abc' => "[FILTERED]", + 'def' => "[FILTERED]", + 'ghi' => "789", + 'nested' => { 'abc' => '[FILTERED]' } } + + notice = build_notice(:params_filters => filters, attribute => original) + + assert_equal(filtered, + notice.send(attribute)) + end + +end diff --git a/vendor/plugins/hoptoad_notifier/test/notifier_test.rb b/vendor/plugins/hoptoad_notifier/test/notifier_test.rb index 1cb6ea0..399d3ea 100644 --- a/vendor/plugins/hoptoad_notifier/test/notifier_test.rb +++ b/vendor/plugins/hoptoad_notifier/test/notifier_test.rb @@ -1,178 +1,221 @@ require File.dirname(__FILE__) + '/helper' -class NotifierTest < ActiveSupport::TestCase - context "Sending a notice" do - context "with an exception" do - setup do - @sender = HoptoadNotifier::Sender.new - @backtrace = caller - @exception = begin - raise - rescue => caught_exception - caught_exception - end - @options = {:error_message => "123", - :backtrace => @backtrace} - HoptoadNotifier.instance_variable_set("@backtrace_filters", []) - HoptoadNotifier::Sender.expects(:new).returns(@sender) - @sender.stubs(:public_environment?).returns(true) - end +class NotifierTest < Test::Unit::TestCase - context "when using an HTTP Proxy" do - setup do - @body = 'body' - @response = stub(:body => @body) - @http = stub(:post => @response, :read_timeout= => nil, :open_timeout= => nil, :use_ssl= => nil) - @sender.stubs(:logger).returns(stub(:error => nil, :info => nil)) - @proxy = stub - @proxy.stubs(:new).returns(@http) - - HoptoadNotifier.port = nil - HoptoadNotifier.host = nil - HoptoadNotifier.secure = false - - Net::HTTP.expects(:Proxy).with( - HoptoadNotifier.proxy_host, - HoptoadNotifier.proxy_port, - HoptoadNotifier.proxy_user, - HoptoadNotifier.proxy_pass - ).returns(@proxy) - end - - context "on notify" do - setup { HoptoadNotifier.notify(@exception) } - - before_should "post to Hoptoad" do - url = "http://hoptoadapp.com:80/notices/" - uri = URI.parse(url) - URI.expects(:parse).with(url).returns(uri) - @http.expects(:post).with(uri.path, anything, anything).returns(@response) - end - end - end + class OriginalException < Exception + end - context "when stubbing out Net::HTTP" do - setup do - @body = 'body' - @response = stub(:body => @body) - @http = stub(:post => @response, :read_timeout= => nil, :open_timeout= => nil, :use_ssl= => nil) - @sender.stubs(:logger).returns(stub(:error => nil, :info => nil)) - Net::HTTP.stubs(:new).returns(@http) - HoptoadNotifier.port = nil - HoptoadNotifier.host = nil - HoptoadNotifier.proxy_host = nil - end - - context "on notify" do - setup { HoptoadNotifier.notify(@exception) } - - before_should "post to the right url for non-ssl" do - HoptoadNotifier.secure = false - url = "http://hoptoadapp.com:80/notices/" - uri = URI.parse(url) - URI.expects(:parse).with(url).returns(uri) - @http.expects(:post).with(uri.path, anything, anything).returns(@response) - end - - before_should "post to the right path" do - @http.expects(:post).with("/notices/", anything, anything).returns(@response) - end - - before_should "call send_to_hoptoad" do - @sender.expects(:send_to_hoptoad) - end - - before_should "default the open timeout to 2 seconds" do - HoptoadNotifier.http_open_timeout = nil - @http.expects(:open_timeout=).with(2) - end - - before_should "default the read timeout to 5 seconds" do - HoptoadNotifier.http_read_timeout = nil - @http.expects(:read_timeout=).with(5) - end - - before_should "allow override of the open timeout" do - HoptoadNotifier.http_open_timeout = 4 - @http.expects(:open_timeout=).with(4) - end - - before_should "allow override of the read timeout" do - HoptoadNotifier.http_read_timeout = 10 - @http.expects(:read_timeout=).with(10) - end - - before_should "connect to the right port for ssl" do - HoptoadNotifier.secure = true - Net::HTTP.expects(:new).with("hoptoadapp.com", 443).returns(@http) - end - - before_should "connect to the right port for non-ssl" do - HoptoadNotifier.secure = false - Net::HTTP.expects(:new).with("hoptoadapp.com", 80).returns(@http) - end - - before_should "use ssl if secure" do - HoptoadNotifier.secure = true - HoptoadNotifier.host = 'example.org' - Net::HTTP.expects(:new).with('example.org', 443).returns(@http) - end - - before_should "not use ssl if not secure" do - HoptoadNotifier.secure = nil - HoptoadNotifier.host = 'example.org' - Net::HTTP.expects(:new).with('example.org', 80).returns(@http) - end - end - end + class ContinuedException < Exception + end - should "send as if it were a normally caught exception" do - @sender.expects(:notify_hoptoad).with(@exception) - HoptoadNotifier.notify(@exception) - end + include DefinesConstants + + def setup + super + reset_config + end + + def assert_sent(notice, notice_args) + assert_received(HoptoadNotifier::Notice, :new) {|expect| expect.with(has_entries(notice_args)) } + assert_received(notice, :to_xml) + assert_received(HoptoadNotifier.sender, :send_to_hoptoad) {|expect| expect.with(notice.to_xml) } + end + + def set_public_env + HoptoadNotifier.configure { |config| config.environment_name = 'production' } + end + + def set_development_env + HoptoadNotifier.configure { |config| config.environment_name = 'development' } + end + + should "yield and save a configuration when configuring" do + yielded_configuration = nil + HoptoadNotifier.configure do |config| + yielded_configuration = config + end + + assert_kind_of HoptoadNotifier::Configuration, yielded_configuration + assert_equal yielded_configuration, HoptoadNotifier.configuration + end + + should "not remove existing config options when configuring twice" do + first_config = nil + HoptoadNotifier.configure do |config| + first_config = config + end + HoptoadNotifier.configure do |config| + assert_equal first_config, config + end + end + + should "configure the sender" do + sender = stub_sender + HoptoadNotifier::Sender.stubs(:new => sender) + configuration = nil + + HoptoadNotifier.configure { |yielded_config| configuration = yielded_config } + + assert_received(HoptoadNotifier::Sender, :new) { |expect| expect.with(configuration) } + assert_equal sender, HoptoadNotifier.sender + end + + should "create and send a notice for an exception" do + set_public_env + exception = build_exception + stub_sender! + notice = stub_notice! + + HoptoadNotifier.notify(exception) + + assert_sent notice, :exception => exception + end + + should "create and send a notice for a hash" do + set_public_env + notice = stub_notice! + notice_args = { :error_message => 'uh oh' } + stub_sender! + + HoptoadNotifier.notify(notice_args) + + assert_sent(notice, notice_args) + end + + should "create and sent a notice for an exception and hash" do + set_public_env + exception = build_exception + notice = stub_notice! + notice_args = { :error_message => 'uh oh' } + stub_sender! + + HoptoadNotifier.notify(exception, notice_args) + + assert_sent(notice, notice_args.merge(:exception => exception)) + end - should "make sure the exception is munged into a hash" do - options = HoptoadNotifier.default_notice_options.merge({ - :backtrace => @exception.backtrace, - :environment => ENV.to_hash, - :error_class => @exception.class.name, - :error_message => "#{@exception.class.name}: #{@exception.message}", - :api_key => HoptoadNotifier.api_key, - }) - @sender.expects(:send_to_hoptoad).with(:notice => options) - HoptoadNotifier.notify(@exception) + should "not create a notice in a development environment" do + set_development_env + sender = stub_sender! + + HoptoadNotifier.notify(build_exception) + HoptoadNotifier.notify_or_ignore(build_exception) + + assert_received(sender, :send_to_hoptoad) {|expect| expect.never } + end + + should "not deliver an ignored exception when notifying implicitly" do + set_public_env + exception = build_exception + sender = stub_sender! + notice = stub_notice! + notice.stubs(:ignore? => true) + + HoptoadNotifier.notify_or_ignore(exception) + + assert_received(sender, :send_to_hoptoad) {|expect| expect.never } + end + + should "deliver an ignored exception when notifying manually" do + set_public_env + exception = build_exception + sender = stub_sender! + notice = stub_notice! + notice.stubs(:ignore? => true) + + HoptoadNotifier.notify(exception) + + assert_sent(notice, :exception => exception) + end + + should "pass config to created notices" do + exception = build_exception + config_opts = { 'one' => 'two', 'three' => 'four' } + notice = stub_notice! + stub_sender! + HoptoadNotifier.configuration = stub('config', :merge => config_opts, :public? => true) + + HoptoadNotifier.notify(exception) + + assert_received(HoptoadNotifier::Notice, :new) do |expect| + expect.with(has_entries(config_opts)) + end + end + + context "building notice JSON for an exception" do + setup do + @params = { :controller => "users", :action => "create" } + @exception = build_exception + @hash = HoptoadNotifier.build_lookup_hash_for(@exception, @params) + end + + should "set action" do + assert_equal @params[:action], @hash[:action] + end + + should "set controller" do + assert_equal @params[:controller], @hash[:component] + end + + should "set line number" do + assert @hash[:line_number] =~ /\d+/ + end + + should "set file" do + assert_match /\/test\/helper\.rb$/, @hash[:file] + end + + should "set rails_env to production" do + assert_equal 'production', @hash[:environment_name] + end + + should "set error class" do + assert_equal 'RuntimeError', @hash[:error_class] + end + + should "not set file or line number with no backtrace" do + @exception.stubs(:backtrace).returns([]) + + @hash = HoptoadNotifier.build_lookup_hash_for(@exception) + + assert_nil @hash[:line_number] + assert_nil @hash[:file] + end + + should "not set action or controller when not provided" do + @hash = HoptoadNotifier.build_lookup_hash_for(@exception) + + assert_nil @hash[:action] + assert_nil @hash[:controller] + end + + context "when an exception that provides #original_exception is raised" do + setup do + @exception.stubs(:original_exception).returns(begin + raise NotifierTest::OriginalException.new + rescue Exception => e + e + end) end - should "parse massive one-line exceptions into multiple lines" do - @original_backtrace = "one big line\n separated\n by new lines\nand some spaces" - @expected_backtrace = ["one big line", "separated", "by new lines", "and some spaces"] - @exception.set_backtrace [@original_backtrace] - - options = HoptoadNotifier.default_notice_options.merge({ - :backtrace => @expected_backtrace, - :environment => ENV.to_hash, - :error_class => @exception.class.name, - :error_message => "#{@exception.class.name}: #{@exception.message}", - :api_key => HoptoadNotifier.api_key, - }) - - @sender.expects(:send_to_hoptoad).with(:notice => options) - HoptoadNotifier.notify(@exception) + should "unwrap exceptions that provide #original_exception" do + @hash = HoptoadNotifier.build_lookup_hash_for(@exception) + assert_equal "NotifierTest::OriginalException", @hash[:error_class] end end - context "without an exception" do + context "when an exception that provides #continued_exception is raised" do setup do - @sender = HoptoadNotifier::Sender.new - @backtrace = caller - @options = {:error_message => "123", - :backtrace => @backtrace} - HoptoadNotifier::Sender.expects(:new).returns(@sender) + @exception.stubs(:continued_exception).returns(begin + raise NotifierTest::ContinuedException.new + rescue Exception => e + e + end) end - should "send sensible defaults" do - @sender.expects(:notify_hoptoad).with(@options) - HoptoadNotifier.notify(:error_message => "123", :backtrace => @backtrace) + should "unwrap exceptions that provide #continued_exception" do + @hash = HoptoadNotifier.build_lookup_hash_for(@exception) + assert_equal "NotifierTest::ContinuedException", @hash[:error_class] end end end diff --git a/vendor/plugins/hoptoad_notifier/test/sender_test.rb b/vendor/plugins/hoptoad_notifier/test/sender_test.rb new file mode 100644 index 0000000..e0a1a8d --- /dev/null +++ b/vendor/plugins/hoptoad_notifier/test/sender_test.rb @@ -0,0 +1,123 @@ +require File.dirname(__FILE__) + '/helper' + +class SenderTest < Test::Unit::TestCase + + def setup + reset_config + end + + def build_sender(opts = {}) + config = HoptoadNotifier::Configuration.new + opts.each {|opt, value| config.send(:"#{opt}=", value) } + HoptoadNotifier::Sender.new(config) + end + + def send_exception(args = {}) + notice = args.delete(:notice) || build_notice_data + sender = args.delete(:sender) || build_sender(args) + sender.send_to_hoptoad(notice) + sender + end + + def stub_http + response = stub(:body => 'body') + http = stub(:post => response, + :read_timeout= => nil, + :open_timeout= => nil, + :use_ssl= => nil) + Net::HTTP.stubs(:new => http) + http + end + + should "post to Hoptoad when using an HTTP proxy" do + response = stub(:body => 'body') + http = stub(:post => response, + :read_timeout= => nil, + :open_timeout= => nil, + :use_ssl= => nil) + proxy = stub(:new => http) + Net::HTTP.stubs(:Proxy => proxy) + + url = "http://hoptoadapp.com:80#{HoptoadNotifier::Sender::NOTICES_URI}" + uri = URI.parse(url) + + proxy_host = 'some.host' + proxy_port = 88 + proxy_user = 'login' + proxy_pass = 'passwd' + + send_exception(:proxy_host => proxy_host, + :proxy_port => proxy_port, + :proxy_user => proxy_user, + :proxy_pass => proxy_pass) + assert_received(http, :post) do |expect| + expect.with(uri.path, anything, HoptoadNotifier::HEADERS) + end + assert_received(Net::HTTP, :Proxy) do |expect| + expect.with(proxy_host, proxy_port, proxy_user, proxy_pass) + end + end + + should "post to the right url for non-ssl" do + http = stub_http + url = "http://hoptoadapp.com:80#{HoptoadNotifier::Sender::NOTICES_URI}" + uri = URI.parse(url) + send_exception(:secure => false) + assert_received(http, :post) {|expect| expect.with(uri.path, anything, HoptoadNotifier::HEADERS) } + end + + should "post to the right path for ssl" do + http = stub_http + send_exception(:secure => true) + assert_received(http, :post) {|expect| expect.with(HoptoadNotifier::Sender::NOTICES_URI, anything, HoptoadNotifier::HEADERS) } + end + + should "default the open timeout to 2 seconds" do + http = stub_http + send_exception + assert_received(http, :open_timeout=) {|expect| expect.with(2) } + end + + should "default the read timeout to 5 seconds" do + http = stub_http + send_exception + assert_received(http, :read_timeout=) {|expect| expect.with(5) } + end + + should "allow override of the open timeout" do + http = stub_http + send_exception(:http_open_timeout => 4) + assert_received(http, :open_timeout=) {|expect| expect.with(4) } + end + + should "allow override of the read timeout" do + http = stub_http + send_exception(:http_read_timeout => 10) + assert_received(http, :read_timeout=) {|expect| expect.with(10) } + end + + should "connect to the right port for ssl" do + stub_http + send_exception(:secure => true) + assert_received(Net::HTTP, :new) {|expect| expect.with("hoptoadapp.com", 443) } + end + + should "connect to the right port for non-ssl" do + stub_http + send_exception(:secure => false) + assert_received(Net::HTTP, :new) {|expect| expect.with("hoptoadapp.com", 80) } + end + + should "use ssl if secure" do + stub_http + send_exception(:secure => true, :host => 'example.org') + assert_received(Net::HTTP, :new) {|expect| expect.with('example.org', 443) } + end + + should "not use ssl if not secure" do + stub_http + send_exception(:secure => false, :host => 'example.org') + assert_received(Net::HTTP, :new) {|expect| expect.with('example.org', 80) } + end + +end -- libgit2 0.21.2