Commit 23a288e7002416df02b2aaa07916caf0c3614dd5

Authored by Pius Uzamere
1 parent 4c5e63b3

updated hoptoad

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