square-hoptoad_notifier 2.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. data/CHANGELOG +427 -0
  2. data/INSTALL +25 -0
  3. data/MIT-LICENSE +22 -0
  4. data/README.md +435 -0
  5. data/README_FOR_HEROKU_ADDON.md +93 -0
  6. data/Rakefile +227 -0
  7. data/SUPPORTED_RAILS_VERSIONS +10 -0
  8. data/TESTING.rdoc +8 -0
  9. data/generators/hoptoad/hoptoad_generator.rb +88 -0
  10. data/generators/hoptoad/lib/insert_commands.rb +34 -0
  11. data/generators/hoptoad/lib/rake_commands.rb +24 -0
  12. data/generators/hoptoad/templates/capistrano_hook.rb +6 -0
  13. data/generators/hoptoad/templates/hoptoad_notifier_tasks.rake +25 -0
  14. data/generators/hoptoad/templates/initializer.rb +6 -0
  15. data/lib/hoptoad_notifier.rb +153 -0
  16. data/lib/hoptoad_notifier/backtrace.rb +99 -0
  17. data/lib/hoptoad_notifier/capistrano.rb +20 -0
  18. data/lib/hoptoad_notifier/configuration.rb +242 -0
  19. data/lib/hoptoad_notifier/notice.rb +337 -0
  20. data/lib/hoptoad_notifier/rack.rb +42 -0
  21. data/lib/hoptoad_notifier/rails.rb +41 -0
  22. data/lib/hoptoad_notifier/rails/action_controller_catcher.rb +30 -0
  23. data/lib/hoptoad_notifier/rails/controller_methods.rb +68 -0
  24. data/lib/hoptoad_notifier/rails/error_lookup.rb +33 -0
  25. data/lib/hoptoad_notifier/rails/javascript_notifier.rb +42 -0
  26. data/lib/hoptoad_notifier/rails3_tasks.rb +82 -0
  27. data/lib/hoptoad_notifier/railtie.rb +32 -0
  28. data/lib/hoptoad_notifier/sender.rb +83 -0
  29. data/lib/hoptoad_notifier/shared_tasks.rb +29 -0
  30. data/lib/hoptoad_notifier/tasks.rb +83 -0
  31. data/lib/hoptoad_notifier/user_informer.rb +23 -0
  32. data/lib/hoptoad_notifier/version.rb +3 -0
  33. data/lib/hoptoad_tasks.rb +44 -0
  34. data/lib/rails/generators/hoptoad/hoptoad_generator.rb +94 -0
  35. data/lib/templates/javascript_notifier.erb +13 -0
  36. data/lib/templates/rescue.erb +91 -0
  37. data/rails/init.rb +1 -0
  38. data/script/integration_test.rb +38 -0
  39. data/test/backtrace_test.rb +118 -0
  40. data/test/catcher_test.rb +331 -0
  41. data/test/configuration_test.rb +216 -0
  42. data/test/helper.rb +248 -0
  43. data/test/hoptoad_tasks_test.rb +152 -0
  44. data/test/javascript_notifier_test.rb +52 -0
  45. data/test/logger_test.rb +85 -0
  46. data/test/notice_test.rb +448 -0
  47. data/test/notifier_test.rb +222 -0
  48. data/test/rack_test.rb +58 -0
  49. data/test/rails_initializer_test.rb +36 -0
  50. data/test/sender_test.rb +161 -0
  51. data/test/user_informer_test.rb +29 -0
  52. metadata +225 -0
@@ -0,0 +1,23 @@
1
+ module HoptoadNotifier
2
+ class UserInformer
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def replacement(with)
8
+ @replacement ||= HoptoadNotifier.configuration.user_information.gsub(/\{\{\s*error_id\s*\}\}/, with.to_s)
9
+ end
10
+
11
+ def call(env)
12
+ status, headers, body = @app.call(env)
13
+ if env['hoptoad.error_id']
14
+ body.each_with_index do |chunk, i|
15
+ body[i] = chunk.to_s.gsub("<!-- HOPTOAD ERROR -->", replacement(env['hoptoad.error_id']))
16
+ end
17
+ headers['Content-Length'] = body.sum(&:length).to_s
18
+ end
19
+ [status, headers, body]
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,3 @@
1
+ module HoptoadNotifier
2
+ VERSION = "2.4.8".freeze
3
+ end
@@ -0,0 +1,44 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'active_support'
4
+
5
+ # Capistrano tasks for notifying Hoptoad of deploys
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
16
+ def self.deploy(opts = {})
17
+ if HoptoadNotifier.configuration.api_key.blank?
18
+ puts "I don't seem to be configured with an API key. Please check your configuration."
19
+ return false
20
+ end
21
+
22
+ if opts[:rails_env].blank?
23
+ puts "I don't know to which Rails environment you are deploying (use the TO=production option)."
24
+ return false
25
+ end
26
+
27
+ params = {'api_key' => opts.delete(:api_key) ||
28
+ HoptoadNotifier.configuration.api_key}
29
+ opts.each {|k,v| params["deploy[#{k}]"] = v }
30
+
31
+ url = URI.parse("http://#{HoptoadNotifier.configuration.host || 'hoptoadapp.com'}/deploys.txt")
32
+
33
+ proxy = Net::HTTP.Proxy(HoptoadNotifier.configuration.proxy_host,
34
+ HoptoadNotifier.configuration.proxy_port,
35
+ HoptoadNotifier.configuration.proxy_user,
36
+ HoptoadNotifier.configuration.proxy_pass)
37
+
38
+ response = proxy.post_form(url, params)
39
+
40
+ puts response.body
41
+ return Net::HTTPSuccess === response
42
+ end
43
+ end
44
+
@@ -0,0 +1,94 @@
1
+ require 'rails/generators'
2
+
3
+ class HoptoadGenerator < Rails::Generators::Base
4
+
5
+ class_option :api_key, :aliases => "-k", :type => :string, :desc => "Your Hoptoad API key"
6
+ class_option :heroku, :type => :boolean, :desc => "Use the Heroku addon to provide your Hoptoad API key"
7
+
8
+ def self.source_root
9
+ @_hoptoad_source_root ||= File.expand_path("../../../../../generators/hoptoad/templates", __FILE__)
10
+ end
11
+
12
+ def install
13
+ ensure_api_key_was_configured
14
+ ensure_plugin_is_not_present
15
+ append_capistrano_hook
16
+ generate_initializer unless api_key_configured?
17
+ determine_api_key if heroku?
18
+ test_hoptoad
19
+ end
20
+
21
+ private
22
+
23
+ def ensure_api_key_was_configured
24
+ if !options[:api_key] && !options[:heroku] && !api_key_configured?
25
+ puts "Must pass --api-key or --heroku or create config/initializers/hoptoad.rb"
26
+ exit
27
+ end
28
+ end
29
+
30
+ def ensure_plugin_is_not_present
31
+ if plugin_is_present?
32
+ puts "You must first remove the hoptoad_notifier plugin. Please run: script/plugin remove hoptoad_notifier"
33
+ exit
34
+ end
35
+ end
36
+
37
+ def append_capistrano_hook
38
+ if File.exists?('config/deploy.rb') && File.exists?('Capfile')
39
+ append_file('config/deploy.rb', <<-HOOK)
40
+
41
+ require './config/boot'
42
+ require 'hoptoad_notifier/capistrano'
43
+ HOOK
44
+ end
45
+ end
46
+
47
+ def api_key_expression
48
+ s = if options[:api_key]
49
+ "'#{options[:api_key]}'"
50
+ elsif options[:heroku]
51
+ "ENV['HOPTOAD_API_KEY']"
52
+ end
53
+ end
54
+
55
+ def generate_initializer
56
+ template 'initializer.rb', 'config/initializers/hoptoad.rb'
57
+ end
58
+
59
+ def determine_api_key
60
+ puts "Attempting to determine your API Key from Heroku..."
61
+ ENV['HOPTOAD_API_KEY'] = heroku_api_key
62
+ if ENV['HOPTOAD_API_KEY'].blank?
63
+ puts "... Failed."
64
+ puts "WARNING: We were unable to detect the Hoptoad API Key from your Heroku environment."
65
+ puts "Your Heroku application environment may not be configured correctly."
66
+ exit 1
67
+ else
68
+ puts "... Done."
69
+ puts "Heroku's Hoptoad API Key is '#{ENV['HOPTOAD_API_KEY']}'"
70
+ end
71
+ end
72
+
73
+ def heroku_api_key
74
+ `heroku console 'puts ENV[%{HOPTOAD_API_KEY}]'`.split("\n").first
75
+ end
76
+
77
+ def heroku?
78
+ options[:heroku] ||
79
+ system("grep HOPTOAD_API_KEY config/initializers/hoptoad.rb") ||
80
+ system("grep HOPTOAD_API_KEY config/environment.rb")
81
+ end
82
+
83
+ def api_key_configured?
84
+ File.exists?('config/initializers/hoptoad.rb')
85
+ end
86
+
87
+ def test_hoptoad
88
+ puts run("rake hoptoad:test --trace")
89
+ end
90
+
91
+ def plugin_is_present?
92
+ File.exists?('vendor/plugins/hoptoad_notifier')
93
+ end
94
+ end
@@ -0,0 +1,13 @@
1
+ <%= javascript_tag %Q{
2
+ var notifierJsScheme = (("https:" == document.location.protocol) ? "https://" : "http://");
3
+ document.write(unescape("%3Cscript src='" + notifierJsScheme + "#{host}/javascripts/notifier.js' type='text/javascript'%3E%3C/script%3E"));
4
+ }
5
+ %>
6
+
7
+ <%= javascript_tag %Q{
8
+ Hoptoad.setKey('#{api_key}');
9
+ Hoptoad.setHost('#{host}');
10
+ Hoptoad.setEnvironment('#{environment}');
11
+ Hoptoad.setErrorDefaults({ url: "#{escape_javascript url}", component: "#{controller_name}", action: "#{action_name}" });
12
+ }
13
+ %>
@@ -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>
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'hoptoad_notifier/rails'
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'logger'
4
+ require 'fileutils'
5
+
6
+ RAILS_ENV = "production"
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?
15
+
16
+ host = ARGV[1]
17
+ host ||= "hoptoadapp.com"
18
+
19
+ secure = (ARGV[2] == "secure")
20
+
21
+ exception = begin
22
+ raise "Testing hoptoad notifier with secure = #{secure}. If you can see this, it works."
23
+ rescue => foo
24
+ foo
25
+ end
26
+
27
+ HoptoadNotifier.configure do |config|
28
+ config.secure = secure
29
+ config.host = host
30
+ config.api_key = ARGV.first
31
+ end
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}"
37
+ HoptoadNotifier.notify(exception)
38
+
@@ -0,0 +1,118 @@
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
+ context "with a blank project root" do
67
+ setup do
68
+ HoptoadNotifier.configure {|config| config.project_root = '' }
69
+ end
70
+
71
+ teardown do
72
+ reset_config
73
+ end
74
+
75
+ should "not filter line numbers with respect to any project root" do
76
+ backtrace = ["/app/models/user.rb:7:in `latest'",
77
+ "/app/controllers/users_controller.rb:13:in `index'",
78
+ "/lib/something.rb:41:in `open'"]
79
+
80
+ backtrace_with_root =
81
+ HoptoadNotifier::Backtrace.parse(backtrace, :filters => default_filters)
82
+
83
+ backtrace_without_root =
84
+ HoptoadNotifier::Backtrace.parse(backtrace)
85
+
86
+ assert_equal backtrace_without_root, backtrace_with_root
87
+ end
88
+ end
89
+
90
+ should "remove notifier trace" do
91
+ inside_notifier = ['lib/hoptoad_notifier.rb:13:in `voodoo`']
92
+ outside_notifier = ['users_controller:8:in `index`']
93
+
94
+ without_inside = HoptoadNotifier::Backtrace.parse(outside_notifier)
95
+ with_inside = HoptoadNotifier::Backtrace.parse(inside_notifier + outside_notifier,
96
+ :filters => default_filters)
97
+
98
+ assert_equal without_inside, with_inside
99
+ end
100
+
101
+ should "run filters on the backtrace" do
102
+ filters = [lambda { |line| line.sub('foo', 'bar') }]
103
+ input = HoptoadNotifier::Backtrace.parse(["foo:13:in `one'", "baz:14:in `two'"],
104
+ :filters => filters)
105
+ expected = HoptoadNotifier::Backtrace.parse(["bar:13:in `one'", "baz:14:in `two'"])
106
+ assert_equal expected, input
107
+ end
108
+
109
+ def build_backtrace_array
110
+ ["app/models/user.rb:13:in `magic'",
111
+ "app/controllers/users_controller.rb:8:in `index'"]
112
+ end
113
+
114
+ def default_filters
115
+ HoptoadNotifier::Configuration::DEFAULT_BACKTRACE_FILTERS
116
+ end
117
+
118
+ end
@@ -0,0 +1,331 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class ActionControllerCatcherTest < 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
+ Class.new(ActionController::Base).tap do |klass|
20
+ klass.__send__(:include, HoptoadNotifier::Rails::ActionControllerCatcher)
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
+ next if key.match(/^hoptoad\./) # We added this key.
29
+
30
+ element_xpath = "#{xpath}/var[@key = '#{key}']"
31
+ if value.respond_to?(:to_hash)
32
+ assert_sent_hash value.to_hash, element_xpath
33
+ else
34
+ assert_sent_element value, element_xpath
35
+ end
36
+ end
37
+ end
38
+
39
+ def assert_sent_element(value, xpath)
40
+ assert_valid_node last_sent_notice_document, xpath, stringify_array_elements(value).to_s
41
+ end
42
+
43
+ def stringify_array_elements(data)
44
+ if data.respond_to?(:to_ary)
45
+ data.collect do |value|
46
+ stringify_array_elements(value)
47
+ end
48
+ else
49
+ data.to_s
50
+ end
51
+ end
52
+
53
+ def assert_sent_request_info_for(request)
54
+ params = request.parameters.to_hash
55
+ assert_sent_hash params, '/notice/request/params'
56
+ assert_sent_element params['controller'], '/notice/request/component'
57
+ assert_sent_element params['action'], '/notice/request/action'
58
+ assert_sent_element url_from_request(request), '/notice/request/url'
59
+ assert_sent_hash request.env, '/notice/request/cgi-data'
60
+ end
61
+
62
+ def url_from_request(request)
63
+ url = "#{request.protocol}#{request.host}"
64
+
65
+ unless [80, 443].include?(request.port)
66
+ url << ":#{request.port}"
67
+ end
68
+
69
+ url << request.request_uri
70
+ url
71
+ end
72
+
73
+ def sender
74
+ HoptoadNotifier.sender
75
+ end
76
+
77
+ def last_sent_notice_xml
78
+ sender.collected.last
79
+ end
80
+
81
+ def last_sent_notice_document
82
+ assert_not_nil xml = last_sent_notice_xml, "No xml was sent"
83
+ Nokogiri::XML.parse(xml)
84
+ end
85
+
86
+ def process_action(opts = {}, &action)
87
+ opts[:request] ||= ActionController::TestRequest.new
88
+ opts[:response] ||= ActionController::TestResponse.new
89
+ klass = build_controller_class do
90
+ cattr_accessor :local
91
+ define_method(:index, &action)
92
+ def local_request?
93
+ local
94
+ end
95
+ end
96
+ if opts[:filters]
97
+ klass.filter_parameter_logging *opts[:filters]
98
+ end
99
+ if opts[:user_agent]
100
+ if opts[:request].respond_to?(:user_agent=)
101
+ opts[:request].user_agent = opts[:user_agent]
102
+ else
103
+ opts[:request].env["HTTP_USER_AGENT"] = opts[:user_agent]
104
+ end
105
+ end
106
+ if opts[:port]
107
+ opts[:request].port = opts[:port]
108
+ end
109
+ klass.consider_all_requests_local = opts[:all_local]
110
+ klass.local = opts[:local]
111
+ controller = klass.new
112
+ controller.stubs(:rescue_action_in_public_without_hoptoad)
113
+ opts[:request].query_parameters = opts[:request].query_parameters.merge(opts[:params] || {})
114
+ opts[:request].session = ActionController::TestSession.new(opts[:session] || {})
115
+ controller.process(opts[:request], opts[:response])
116
+ controller
117
+ end
118
+
119
+ def process_action_with_manual_notification(args = {})
120
+ process_action(args) do
121
+ notify_hoptoad(:error_message => 'fail')
122
+ # Rails will raise a template error if we don't render something
123
+ render :nothing => true
124
+ end
125
+ end
126
+
127
+ def process_action_with_automatic_notification(args = {})
128
+ process_action(args) { raise "Hello" }
129
+ end
130
+
131
+ should "deliver notices from exceptions raised in public requests" do
132
+ process_action_with_automatic_notification
133
+ assert_caught_and_sent
134
+ end
135
+
136
+ should "not deliver notices from exceptions in local requests" do
137
+ process_action_with_automatic_notification(:local => true)
138
+ assert_caught_and_not_sent
139
+ end
140
+
141
+ should "not deliver notices from exceptions when all requests are local" do
142
+ process_action_with_automatic_notification(:all_local => true)
143
+ assert_caught_and_not_sent
144
+ end
145
+
146
+ should "not deliver notices from actions that don't raise" do
147
+ controller = process_action { render :text => 'Hello' }
148
+ assert_caught_and_not_sent
149
+ assert_equal 'Hello', controller.response.body
150
+ end
151
+
152
+ should "not deliver ignored exceptions raised by actions" do
153
+ ignore(RuntimeError)
154
+ process_action_with_automatic_notification
155
+ assert_caught_and_not_sent
156
+ end
157
+
158
+ should "deliver ignored exception raised manually" do
159
+ ignore(RuntimeError)
160
+ process_action_with_manual_notification
161
+ assert_caught_and_sent
162
+ end
163
+
164
+ should "deliver manually sent notices in public requests" do
165
+ process_action_with_manual_notification
166
+ assert_caught_and_sent
167
+ end
168
+
169
+ should "not deliver manually sent notices in local requests" do
170
+ process_action_with_manual_notification(:local => true)
171
+ assert_caught_and_not_sent
172
+ end
173
+
174
+ should "not deliver manually sent notices when all requests are local" do
175
+ process_action_with_manual_notification(:all_local => true)
176
+ assert_caught_and_not_sent
177
+ end
178
+
179
+ should "continue with default behavior after delivering an exception" do
180
+ controller = process_action_with_automatic_notification(:public => true)
181
+ # TODO: can we test this without stubbing?
182
+ assert_received(controller, :rescue_action_in_public_without_hoptoad)
183
+ end
184
+
185
+ should "not create actions from Hoptoad methods" do
186
+ controller = build_controller_class.new
187
+ assert_equal [], HoptoadNotifier::Rails::ActionControllerCatcher.instance_methods
188
+ end
189
+
190
+ should "ignore exceptions when user agent is being ignored by regular expression" do
191
+ HoptoadNotifier.configuration.ignore_user_agent_only = [/Ignored/]
192
+ process_action_with_automatic_notification(:user_agent => 'ShouldBeIgnored')
193
+ assert_caught_and_not_sent
194
+ end
195
+
196
+ should "ignore exceptions when user agent is being ignored by string" do
197
+ HoptoadNotifier.configuration.ignore_user_agent_only = ['IgnoredUserAgent']
198
+ process_action_with_automatic_notification(:user_agent => 'IgnoredUserAgent')
199
+ assert_caught_and_not_sent
200
+ end
201
+
202
+ should "not ignore exceptions when user agent is not being ignored" do
203
+ HoptoadNotifier.configuration.ignore_user_agent_only = ['IgnoredUserAgent']
204
+ process_action_with_automatic_notification(:user_agent => 'NonIgnoredAgent')
205
+ assert_caught_and_sent
206
+ end
207
+
208
+ should "send session data for manual notifications" do
209
+ data = { 'one' => 'two' }
210
+ process_action_with_manual_notification(:session => data)
211
+ assert_sent_hash data, "/notice/request/session"
212
+ end
213
+
214
+ should "send session data for automatic notification" do
215
+ data = { 'one' => 'two' }
216
+ process_action_with_automatic_notification(:session => data)
217
+ assert_sent_hash data, "/notice/request/session"
218
+ end
219
+
220
+ should "send request data for manual notification" do
221
+ params = { 'controller' => "hoptoad_test", 'action' => "index" }
222
+ controller = process_action_with_manual_notification(:params => params)
223
+ assert_sent_request_info_for controller.request
224
+ end
225
+
226
+ should "send request data for manual notification with non-standard port" do
227
+ params = { 'controller' => "hoptoad_test", 'action' => "index" }
228
+ controller = process_action_with_manual_notification(:params => params, :port => 81)
229
+ assert_sent_request_info_for controller.request
230
+ end
231
+
232
+ should "send request data for automatic notification" do
233
+ params = { 'controller' => "hoptoad_test", 'action' => "index" }
234
+ controller = process_action_with_automatic_notification(:params => params)
235
+ assert_sent_request_info_for controller.request
236
+ end
237
+
238
+ should "send request data for automatic notification with non-standard port" do
239
+ params = { 'controller' => "hoptoad_test", 'action' => "index" }
240
+ controller = process_action_with_automatic_notification(:params => params, :port => 81)
241
+ assert_sent_request_info_for controller.request
242
+ end
243
+
244
+ should "use standard rails logging filters on params and session and env" do
245
+ filtered_params = { "abc" => "123",
246
+ "def" => "456",
247
+ "ghi" => "[FILTERED]" }
248
+ filtered_session = { "abc" => "123",
249
+ "ghi" => "[FILTERED]" }
250
+ ENV['ghi'] = 'abc'
251
+ filtered_env = { 'ghi' => '[FILTERED]' }
252
+ filtered_cgi = { 'REQUEST_METHOD' => '[FILTERED]' }
253
+
254
+ process_action_with_automatic_notification(:filters => [:ghi, :request_method],
255
+ :params => { "abc" => "123",
256
+ "def" => "456",
257
+ "ghi" => "789" },
258
+ :session => { "abc" => "123",
259
+ "ghi" => "789" })
260
+ assert_sent_hash filtered_params, '/notice/request/params'
261
+ assert_sent_hash filtered_cgi, '/notice/request/cgi-data'
262
+ assert_sent_hash filtered_session, '/notice/request/session'
263
+ end
264
+
265
+ context "for a local error with development lookup enabled" do
266
+ setup do
267
+ HoptoadNotifier.configuration.development_lookup = true
268
+ HoptoadNotifier.stubs(:build_lookup_hash_for).returns({ :awesome => 2 })
269
+
270
+ @controller = process_action_with_automatic_notification(:local => true)
271
+ @response = @controller.response
272
+ end
273
+
274
+ should "append custom CSS and JS to response body for a local error" do
275
+ assert_match /text\/css/, @response.body
276
+ assert_match /text\/javascript/, @response.body
277
+ end
278
+
279
+ should "contain host, API key and notice JSON" do
280
+ assert_match HoptoadNotifier.configuration.host.to_json, @response.body
281
+ assert_match HoptoadNotifier.configuration.api_key.to_json, @response.body
282
+ assert_match ({ :awesome => 2 }).to_json, @response.body
283
+ end
284
+ end
285
+
286
+ context "for a local error with development lookup disabled" do
287
+ setup do
288
+ HoptoadNotifier.configuration.development_lookup = false
289
+
290
+ @controller = process_action_with_automatic_notification(:local => true)
291
+ @response = @controller.response
292
+ end
293
+
294
+ should "not append custom CSS and JS to response for a local error" do
295
+ assert_no_match /text\/css/, @response.body
296
+ assert_no_match /text\/javascript/, @response.body
297
+ end
298
+ end
299
+
300
+ should "call session.to_hash if available" do
301
+ hash_data = {:key => :value}
302
+
303
+ session = ActionController::TestSession.new
304
+ ActionController::TestSession.stubs(:new).returns(session)
305
+ session.stubs(:to_hash).returns(hash_data)
306
+
307
+ process_action_with_automatic_notification
308
+ assert_received(session, :to_hash)
309
+ assert_received(session, :data) { |expect| expect.never }
310
+ assert_caught_and_sent
311
+ end
312
+
313
+ should "call session.data if session.to_hash is undefined" do
314
+ hash_data = {:key => :value}
315
+
316
+ session = ActionController::TestSession.new
317
+ ActionController::TestSession.stubs(:new).returns(session)
318
+ session.stubs(:data).returns(hash_data)
319
+ if session.respond_to?(:to_hash)
320
+ class << session
321
+ undef to_hash
322
+ end
323
+ end
324
+
325
+ process_action_with_automatic_notification
326
+ assert_received(session, :to_hash) { |expect| expect.never }
327
+ assert_received(session, :data) { |expect| expect.at_least_once }
328
+ assert_caught_and_sent
329
+ end
330
+
331
+ end