projectlocker_pulse 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/CHANGELOG +26 -0
  2. data/Gemfile +3 -0
  3. data/Guardfile +6 -0
  4. data/INSTALL +20 -0
  5. data/MIT-LICENSE +23 -0
  6. data/README.md +439 -0
  7. data/README_FOR_HEROKU_ADDON.md +89 -0
  8. data/Rakefile +223 -0
  9. data/SUPPORTED_RAILS_VERSIONS +38 -0
  10. data/TESTING.md +41 -0
  11. data/features/metal.feature +18 -0
  12. data/features/rack.feature +60 -0
  13. data/features/rails.feature +272 -0
  14. data/features/rails_with_js_notifier.feature +97 -0
  15. data/features/rake.feature +27 -0
  16. data/features/sinatra.feature +29 -0
  17. data/features/step_definitions/file_steps.rb +10 -0
  18. data/features/step_definitions/metal_steps.rb +23 -0
  19. data/features/step_definitions/rack_steps.rb +23 -0
  20. data/features/step_definitions/rails_application_steps.rb +478 -0
  21. data/features/step_definitions/rake_steps.rb +17 -0
  22. data/features/support/env.rb +18 -0
  23. data/features/support/matchers.rb +35 -0
  24. data/features/support/projectlocker_pulse_shim.rb.template +16 -0
  25. data/features/support/rails.rb +201 -0
  26. data/features/support/rake/Rakefile +68 -0
  27. data/features/support/terminal.rb +107 -0
  28. data/features/user_informer.feature +63 -0
  29. data/generators/pulse/lib/insert_commands.rb +34 -0
  30. data/generators/pulse/lib/rake_commands.rb +24 -0
  31. data/generators/pulse/pulse_generator.rb +94 -0
  32. data/generators/pulse/templates/capistrano_hook.rb +6 -0
  33. data/generators/pulse/templates/initializer.rb +6 -0
  34. data/generators/pulse/templates/pulse_tasks.rake +25 -0
  35. data/install.rb +1 -0
  36. data/lib/projectlocker_pulse.rb +159 -0
  37. data/lib/pulse/backtrace.rb +108 -0
  38. data/lib/pulse/capistrano.rb +43 -0
  39. data/lib/pulse/configuration.rb +305 -0
  40. data/lib/pulse/notice.rb +390 -0
  41. data/lib/pulse/rack.rb +54 -0
  42. data/lib/pulse/rails/action_controller_catcher.rb +30 -0
  43. data/lib/pulse/rails/controller_methods.rb +85 -0
  44. data/lib/pulse/rails/error_lookup.rb +33 -0
  45. data/lib/pulse/rails/javascript_notifier.rb +47 -0
  46. data/lib/pulse/rails/middleware/exceptions_catcher.rb +33 -0
  47. data/lib/pulse/rails.rb +40 -0
  48. data/lib/pulse/rails3_tasks.rb +99 -0
  49. data/lib/pulse/railtie.rb +49 -0
  50. data/lib/pulse/rake_handler.rb +65 -0
  51. data/lib/pulse/sender.rb +128 -0
  52. data/lib/pulse/shared_tasks.rb +47 -0
  53. data/lib/pulse/tasks.rb +83 -0
  54. data/lib/pulse/user_informer.rb +27 -0
  55. data/lib/pulse/utils/blank.rb +53 -0
  56. data/lib/pulse/version.rb +3 -0
  57. data/lib/pulse_tasks.rb +64 -0
  58. data/lib/rails/generators/pulse/pulse_generator.rb +100 -0
  59. data/lib/templates/javascript_notifier.erb +15 -0
  60. data/lib/templates/rescue.erb +91 -0
  61. data/pulse.gemspec +39 -0
  62. data/rails/init.rb +1 -0
  63. data/resources/README.md +34 -0
  64. data/resources/ca-bundle.crt +3376 -0
  65. data/script/integration_test.rb +38 -0
  66. data/test/backtrace_test.rb +162 -0
  67. data/test/capistrano_test.rb +34 -0
  68. data/test/catcher_test.rb +333 -0
  69. data/test/configuration_test.rb +236 -0
  70. data/test/helper.rb +263 -0
  71. data/test/javascript_notifier_test.rb +51 -0
  72. data/test/logger_test.rb +79 -0
  73. data/test/notice_test.rb +490 -0
  74. data/test/notifier_test.rb +276 -0
  75. data/test/projectlocker_pulse_tasks_test.rb +170 -0
  76. data/test/pulse.xsd +88 -0
  77. data/test/rack_test.rb +58 -0
  78. data/test/rails_initializer_test.rb +36 -0
  79. data/test/recursion_test.rb +10 -0
  80. data/test/sender_test.rb +288 -0
  81. data/test/user_informer_test.rb +29 -0
  82. metadata +432 -0
@@ -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 'projectlocker_pulse'
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 ||= "errors.projectlocker.com"
18
+
19
+ secure = (ARGV[2] == "secure")
20
+
21
+ exception = begin
22
+ raise "Testing Pulse notifier with secure = #{secure}. If you can see this, it works."
23
+ rescue => foo
24
+ foo
25
+ end
26
+
27
+ Pulse.configure do |config|
28
+ config.secure = secure
29
+ config.host = host
30
+ config.api_key = ARGV.first
31
+ end
32
+ puts "Configuration:"
33
+ Pulse.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
+ Pulse.notify(exception)
38
+
@@ -0,0 +1,162 @@
1
+ require File.expand_path '../helper', __FILE__
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 = Pulse::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 "parse a windows backtrace into lines" do
25
+ array = [
26
+ "C:/Program Files/Server/app/models/user.rb:13:in `magic'",
27
+ "C:/Program Files/Server/app/controllers/users_controller.rb:8:in `index'"
28
+ ]
29
+
30
+ backtrace = Pulse::Backtrace.parse(array)
31
+
32
+ line = backtrace.lines.first
33
+ assert_equal '13', line.number
34
+ assert_equal 'C:/Program Files/Server/app/models/user.rb', line.file
35
+ assert_equal 'magic', line.method
36
+
37
+ line = backtrace.lines.last
38
+ assert_equal '8', line.number
39
+ assert_equal 'C:/Program Files/Server/app/controllers/users_controller.rb', line.file
40
+ assert_equal 'index', line.method
41
+ end
42
+
43
+ should "be equal with equal lines" do
44
+ one = build_backtrace_array
45
+ two = one.dup
46
+
47
+ assert_equal Pulse::Backtrace.parse(one), Pulse::Backtrace.parse(two)
48
+ end
49
+
50
+ should "parse massive one-line exceptions into multiple lines" do
51
+ original_backtrace = Pulse::Backtrace.
52
+ parse(["one:1:in `one'\n two:2:in `two'\n three:3:in `three`"])
53
+ expected_backtrace = Pulse::Backtrace.
54
+ parse(["one:1:in `one'", "two:2:in `two'", "three:3:in `three`"])
55
+
56
+ assert_equal expected_backtrace, original_backtrace
57
+ end
58
+
59
+ context "with a project root" do
60
+ setup do
61
+ @project_root = '/some/path'
62
+ Pulse.configure {|config| config.project_root = @project_root }
63
+ end
64
+
65
+ teardown do
66
+ reset_config
67
+ end
68
+
69
+ should "filter out the project root" do
70
+ backtrace_with_root = Pulse::Backtrace.parse(
71
+ ["#{@project_root}/app/models/user.rb:7:in `latest'",
72
+ "#{@project_root}/app/controllers/users_controller.rb:13:in `index'",
73
+ "/lib/something.rb:41:in `open'"],
74
+ :filters => default_filters)
75
+ backtrace_without_root = Pulse::Backtrace.parse(
76
+ ["[PROJECT_ROOT]/app/models/user.rb:7:in `latest'",
77
+ "[PROJECT_ROOT]/app/controllers/users_controller.rb:13:in `index'",
78
+ "/lib/something.rb:41:in `open'"])
79
+
80
+ assert_equal backtrace_without_root, backtrace_with_root
81
+ end
82
+ end
83
+
84
+ context "with a project root equals to a part of file name" do
85
+ setup do
86
+ # Heroku-like
87
+ @project_root = '/app'
88
+ Pulse.configure {|config| config.project_root = @project_root }
89
+ end
90
+
91
+ teardown do
92
+ reset_config
93
+ end
94
+
95
+ should "filter out the project root" do
96
+ backtrace_with_root = Pulse::Backtrace.parse(
97
+ ["#{@project_root}/app/models/user.rb:7:in `latest'",
98
+ "#{@project_root}/app/controllers/users_controller.rb:13:in `index'",
99
+ "/lib/something.rb:41:in `open'"],
100
+ :filters => default_filters)
101
+ backtrace_without_root = Pulse::Backtrace.parse(
102
+ ["[PROJECT_ROOT]/app/models/user.rb:7:in `latest'",
103
+ "[PROJECT_ROOT]/app/controllers/users_controller.rb:13:in `index'",
104
+ "/lib/something.rb:41:in `open'"])
105
+
106
+ assert_equal backtrace_without_root, backtrace_with_root
107
+ end
108
+ end
109
+
110
+ context "with a blank project root" do
111
+ setup do
112
+ Pulse.configure {|config| config.project_root = '' }
113
+ end
114
+
115
+ teardown do
116
+ reset_config
117
+ end
118
+
119
+ should "not filter line numbers with respect to any project root" do
120
+ backtrace = ["/app/models/user.rb:7:in `latest'",
121
+ "/app/controllers/users_controller.rb:13:in `index'",
122
+ "/lib/something.rb:41:in `open'"]
123
+
124
+ backtrace_with_root =
125
+ Pulse::Backtrace.parse(backtrace, :filters => default_filters)
126
+
127
+ backtrace_without_root =
128
+ Pulse::Backtrace.parse(backtrace)
129
+
130
+ assert_equal backtrace_without_root, backtrace_with_root
131
+ end
132
+ end
133
+
134
+ should "remove notifier trace" do
135
+ inside_notifier = ['lib/pulse.rb:13:in `voodoo`']
136
+ outside_notifier = ['users_controller:8:in `index`']
137
+
138
+ without_inside = Pulse::Backtrace.parse(outside_notifier)
139
+ with_inside = Pulse::Backtrace.parse(inside_notifier + outside_notifier,
140
+ :filters => default_filters)
141
+
142
+ assert_equal without_inside, with_inside
143
+ end
144
+
145
+ should "run filters on the backtrace" do
146
+ filters = [lambda { |line| line.sub('foo', 'bar') }]
147
+ input = Pulse::Backtrace.parse(["foo:13:in `one'", "baz:14:in `two'"],
148
+ :filters => filters)
149
+ expected = Pulse::Backtrace.parse(["bar:13:in `one'", "baz:14:in `two'"])
150
+ assert_equal expected, input
151
+ end
152
+
153
+ def build_backtrace_array
154
+ ["app/models/user.rb:13:in `magic'",
155
+ "app/controllers/users_controller.rb:8:in `index'"]
156
+ end
157
+
158
+ def default_filters
159
+ Pulse::Configuration::DEFAULT_BACKTRACE_FILTERS
160
+ end
161
+
162
+ end
@@ -0,0 +1,34 @@
1
+ require File.expand_path '../helper', __FILE__
2
+
3
+ require 'capistrano/configuration'
4
+ require 'pulse/capistrano'
5
+
6
+ class CapistranoTest < Test::Unit::TestCase
7
+ def setup
8
+ super
9
+ reset_config
10
+
11
+ @configuration = Capistrano::Configuration.new
12
+ Pulse::Capistrano.load_into(@configuration)
13
+ @configuration.dry_run = true
14
+ end
15
+
16
+ should "define pulse:deploy task" do
17
+ assert_not_nil @configuration.find_task('pulse:deploy')
18
+ end
19
+
20
+ should "log when calling pulse:deploy task" do
21
+ @configuration.set(:current_revision, '084505b1c0e0bcf1526e673bb6ac99fbcb18aecc')
22
+ @configuration.set(:repository, 'repository')
23
+ @configuration.set(:current_release, '/home/deploy/rails_app/hoptoad')
24
+ io = StringIO.new
25
+ logger = Capistrano::Logger.new(:output => io)
26
+ logger.level = Capistrano::Logger::MAX_LEVEL
27
+
28
+ @configuration.logger = logger
29
+ @configuration.find_and_execute_task('pulse:deploy')
30
+
31
+ assert io.string.include?('** Notifying Pulse of Deploy')
32
+ assert io.string.include?('** Pulse Notification Complete')
33
+ end
34
+ end
@@ -0,0 +1,333 @@
1
+ require File.expand_path '../helper', __FILE__
2
+
3
+ class ActionControllerCatcherTest < Test::Unit::TestCase
4
+
5
+ include DefinesConstants
6
+
7
+ def setup
8
+ super
9
+ reset_config
10
+ Pulse.sender = CollectingSender.new
11
+ define_constant('RAILS_ROOT', '/path/to/rails/root')
12
+ end
13
+
14
+ def ignore(exception_class)
15
+ Pulse.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, Pulse::Rails::ActionControllerCatcher)
21
+ klass.class_eval(&definition) if definition
22
+ define_constant('PulseTestController', klass)
23
+ end
24
+ end
25
+
26
+ def assert_sent_hash(hash, xpath)
27
+ hash.each do |key, value|
28
+ next if key.match(/^pulse\./) # 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
+ Pulse.sender
75
+ end
76
+
77
+ def last_sent_notice_xml
78
+ sender.collected.last.to_xml
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_pulse)
113
+ opts[:request].query_parameters = opts[:request].query_parameters.merge(opts[:params] || {})
114
+ opts[:request].session = ActionController::TestSession.new(opts[:session] || {})
115
+ # Prevents request.fullpath from crashing Rails in tests
116
+ opts[:request].env['REQUEST_URI'] = opts[:request].request_uri
117
+ controller.process(opts[:request], opts[:response])
118
+ controller
119
+ end
120
+
121
+ def process_action_with_manual_notification(args = {})
122
+ process_action(args) do
123
+ notify_pulse(:error_message => 'fail')
124
+ # Rails will raise a template error if we don't render something
125
+ render :nothing => true
126
+ end
127
+ end
128
+
129
+ def process_action_with_automatic_notification(args = {})
130
+ process_action(args) { raise "Hello" }
131
+ end
132
+
133
+ should "deliver notices from exceptions raised in public requests" do
134
+ process_action_with_automatic_notification
135
+ assert_caught_and_sent
136
+ end
137
+
138
+ should "not deliver notices from exceptions in local requests" do
139
+ process_action_with_automatic_notification(:local => true)
140
+ assert_caught_and_not_sent
141
+ end
142
+
143
+ should "not deliver notices from exceptions when all requests are local" do
144
+ process_action_with_automatic_notification(:all_local => true)
145
+ assert_caught_and_not_sent
146
+ end
147
+
148
+ should "not deliver notices from actions that don't raise" do
149
+ controller = process_action { render :text => 'Hello' }
150
+ assert_caught_and_not_sent
151
+ assert_equal 'Hello', controller.response.body
152
+ end
153
+
154
+ should "not deliver ignored exceptions raised by actions" do
155
+ ignore(RuntimeError)
156
+ process_action_with_automatic_notification
157
+ assert_caught_and_not_sent
158
+ end
159
+
160
+ should "deliver ignored exception raised manually" do
161
+ ignore(RuntimeError)
162
+ process_action_with_manual_notification
163
+ assert_caught_and_sent
164
+ end
165
+
166
+ should "deliver manually sent notices in public requests" do
167
+ process_action_with_manual_notification
168
+ assert_caught_and_sent
169
+ end
170
+
171
+ should "not deliver manually sent notices in local requests" do
172
+ process_action_with_manual_notification(:local => true)
173
+ assert_caught_and_not_sent
174
+ end
175
+
176
+ should "not deliver manually sent notices when all requests are local" do
177
+ process_action_with_manual_notification(:all_local => true)
178
+ assert_caught_and_not_sent
179
+ end
180
+
181
+ should "continue with default behavior after delivering an exception" do
182
+ controller = process_action_with_automatic_notification(:public => true)
183
+ # TODO: can we test this without stubbing?
184
+ assert_received(controller, :rescue_action_in_public_without_pulse)
185
+ end
186
+
187
+ should "not create actions from Pulse methods" do
188
+ controller = build_controller_class.new
189
+ assert_equal [], Pulse::Rails::ActionControllerCatcher.instance_methods
190
+ end
191
+
192
+ should "ignore exceptions when user agent is being ignored by regular expression" do
193
+ Pulse.configuration.ignore_user_agent_only = [/Ignored/]
194
+ process_action_with_automatic_notification(:user_agent => 'ShouldBeIgnored')
195
+ assert_caught_and_not_sent
196
+ end
197
+
198
+ should "ignore exceptions when user agent is being ignored by string" do
199
+ Pulse.configuration.ignore_user_agent_only = ['IgnoredUserAgent']
200
+ process_action_with_automatic_notification(:user_agent => 'IgnoredUserAgent')
201
+ assert_caught_and_not_sent
202
+ end
203
+
204
+ should "not ignore exceptions when user agent is not being ignored" do
205
+ Pulse.configuration.ignore_user_agent_only = ['IgnoredUserAgent']
206
+ process_action_with_automatic_notification(:user_agent => 'NonIgnoredAgent')
207
+ assert_caught_and_sent
208
+ end
209
+
210
+ should "send session data for manual notifications" do
211
+ data = { 'one' => 'two' }
212
+ process_action_with_manual_notification(:session => data)
213
+ assert_sent_hash data, "/notice/request/session"
214
+ end
215
+
216
+ should "send session data for automatic notification" do
217
+ data = { 'one' => 'two' }
218
+ process_action_with_automatic_notification(:session => data)
219
+ assert_sent_hash data, "/notice/request/session"
220
+ end
221
+
222
+ should "send request data for manual notification" do
223
+ params = { 'controller' => "pulse_test", 'action' => "index" }
224
+ controller = process_action_with_manual_notification(:params => params)
225
+ assert_sent_request_info_for controller.request
226
+ end
227
+
228
+ should "send request data for manual notification with non-standard port" do
229
+ params = { 'controller' => "pulse_test", 'action' => "index" }
230
+ controller = process_action_with_manual_notification(:params => params, :port => 81)
231
+ assert_sent_request_info_for controller.request
232
+ end
233
+
234
+ should "send request data for automatic notification" do
235
+ params = { 'controller' => "pulse_test", 'action' => "index" }
236
+ controller = process_action_with_automatic_notification(:params => params)
237
+ assert_sent_request_info_for controller.request
238
+ end
239
+
240
+ should "send request data for automatic notification with non-standard port" do
241
+ params = { 'controller' => "pulse_test", 'action' => "index" }
242
+ controller = process_action_with_automatic_notification(:params => params, :port => 81)
243
+ assert_sent_request_info_for controller.request
244
+ end
245
+
246
+ should "use standard rails logging filters on params and session and env" do
247
+ filtered_params = { "abc" => "123",
248
+ "def" => "456",
249
+ "ghi" => "[FILTERED]" }
250
+ filtered_session = { "abc" => "123",
251
+ "ghi" => "[FILTERED]" }
252
+ ENV['ghi'] = 'abc'
253
+ filtered_env = { 'ghi' => '[FILTERED]' }
254
+ filtered_cgi = { 'REQUEST_METHOD' => '[FILTERED]' }
255
+
256
+ process_action_with_automatic_notification(:filters => [:ghi, :request_method],
257
+ :params => { "abc" => "123",
258
+ "def" => "456",
259
+ "ghi" => "789" },
260
+ :session => { "abc" => "123",
261
+ "ghi" => "789" })
262
+ assert_sent_hash filtered_params, '/notice/request/params'
263
+ assert_sent_hash filtered_cgi, '/notice/request/cgi-data'
264
+ assert_sent_hash filtered_session, '/notice/request/session'
265
+ end
266
+
267
+ context "for a local error with development lookup enabled" do
268
+ setup do
269
+ Pulse.configuration.development_lookup = true
270
+ Pulse.stubs(:build_lookup_hash_for).returns({ :awesome => 2 })
271
+
272
+ @controller = process_action_with_automatic_notification(:local => true)
273
+ @response = @controller.response
274
+ end
275
+
276
+ should "append custom CSS and JS to response body for a local error" do
277
+ assert_match /text\/css/, @response.body
278
+ assert_match /text\/javascript/, @response.body
279
+ end
280
+
281
+ should "contain host, API key and notice JSON" do
282
+ assert_match Pulse.configuration.host.to_json, @response.body
283
+ assert_match Pulse.configuration.api_key.to_json, @response.body
284
+ assert_match ({ :awesome => 2 }).to_json, @response.body
285
+ end
286
+ end
287
+
288
+ context "for a local error with development lookup disabled" do
289
+ setup do
290
+ Pulse.configuration.development_lookup = false
291
+
292
+ @controller = process_action_with_automatic_notification(:local => true)
293
+ @response = @controller.response
294
+ end
295
+
296
+ should "not append custom CSS and JS to response for a local error" do
297
+ assert_no_match /text\/css/, @response.body
298
+ assert_no_match /text\/javascript/, @response.body
299
+ end
300
+ end
301
+
302
+ should "call session.to_hash if available" do
303
+ hash_data = {:key => :value}
304
+
305
+ session = ActionController::TestSession.new
306
+ ActionController::TestSession.stubs(:new).returns(session)
307
+ session.stubs(:to_hash).returns(hash_data)
308
+
309
+ process_action_with_automatic_notification
310
+ assert_received(session, :to_hash)
311
+ assert_received(session, :data) { |expect| expect.never }
312
+ assert_caught_and_sent
313
+ end
314
+
315
+ should "call session.data if session.to_hash is undefined" do
316
+ hash_data = {:key => :value}
317
+
318
+ session = ActionController::TestSession.new
319
+ ActionController::TestSession.stubs(:new).returns(session)
320
+ session.stubs(:data).returns(hash_data)
321
+ if session.respond_to?(:to_hash)
322
+ class << session
323
+ undef to_hash
324
+ end
325
+ end
326
+
327
+ process_action_with_automatic_notification
328
+ assert_received(session, :to_hash) { |expect| expect.never }
329
+ assert_received(session, :data) { |expect| expect.at_least_once }
330
+ assert_caught_and_sent
331
+ end
332
+
333
+ end