cloudtrapper 0.0.2.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/CHANGELOG +823 -0
  2. data/Gemfile +12 -0
  3. data/Guardfile +6 -0
  4. data/INSTALL +20 -0
  5. data/MIT-LICENSE +22 -0
  6. data/README.md +465 -0
  7. data/README_FOR_HEROKU_ADDON.md +94 -0
  8. data/Rakefile +223 -0
  9. data/SUPPORTED_RAILS_VERSIONS +23 -0
  10. data/TESTING.md +33 -0
  11. data/cloudtrapper.gemspec +35 -0
  12. data/features/metal.feature +18 -0
  13. data/features/rack.feature +56 -0
  14. data/features/rails.feature +211 -0
  15. data/features/rails_with_js_notifier.feature +97 -0
  16. data/features/rake.feature +27 -0
  17. data/features/sinatra.feature +29 -0
  18. data/features/step_definitions/file_steps.rb +10 -0
  19. data/features/step_definitions/metal_steps.rb +23 -0
  20. data/features/step_definitions/rack_steps.rb +23 -0
  21. data/features/step_definitions/rails_application_steps.rb +433 -0
  22. data/features/step_definitions/rake_steps.rb +17 -0
  23. data/features/support/airbrake_shim.rb.template +11 -0
  24. data/features/support/env.rb +18 -0
  25. data/features/support/matchers.rb +35 -0
  26. data/features/support/rails.rb +201 -0
  27. data/features/support/rake/Rakefile +68 -0
  28. data/features/support/terminal.rb +107 -0
  29. data/features/user_informer.feature +63 -0
  30. data/generators/cloudtrapper/airbrake_generator.rb +94 -0
  31. data/generators/cloudtrapper/lib/insert_commands.rb +34 -0
  32. data/generators/cloudtrapper/lib/rake_commands.rb +24 -0
  33. data/generators/cloudtrapper/templates/capistrano_hook.rb +6 -0
  34. data/generators/cloudtrapper/templates/cloudtrapper_tasks.rake +25 -0
  35. data/generators/cloudtrapper/templates/initializer.rb +6 -0
  36. data/install.rb +1 -0
  37. data/lib/cloudtrapper/backtrace.rb +100 -0
  38. data/lib/cloudtrapper/capistrano.rb +44 -0
  39. data/lib/cloudtrapper/configuration.rb +281 -0
  40. data/lib/cloudtrapper/notice.rb +348 -0
  41. data/lib/cloudtrapper/rack.rb +55 -0
  42. data/lib/cloudtrapper/rails/action_controller_catcher.rb +30 -0
  43. data/lib/cloudtrapper/rails/controller_methods.rb +74 -0
  44. data/lib/cloudtrapper/rails/error_lookup.rb +33 -0
  45. data/lib/cloudtrapper/rails/javascript_notifier.rb +48 -0
  46. data/lib/cloudtrapper/rails/middleware/exceptions_catcher.rb +29 -0
  47. data/lib/cloudtrapper/rails.rb +40 -0
  48. data/lib/cloudtrapper/rails3_tasks.rb +85 -0
  49. data/lib/cloudtrapper/railtie.rb +48 -0
  50. data/lib/cloudtrapper/rake_handler.rb +66 -0
  51. data/lib/cloudtrapper/sender.rb +116 -0
  52. data/lib/cloudtrapper/shared_tasks.rb +36 -0
  53. data/lib/cloudtrapper/tasks.rb +83 -0
  54. data/lib/cloudtrapper/user_informer.rb +27 -0
  55. data/lib/cloudtrapper/version.rb +3 -0
  56. data/lib/cloudtrapper.rb +155 -0
  57. data/lib/cloudtrapper_tasks.rb +65 -0
  58. data/lib/rails/generators/cloudtrapper/cloudtrapper_generator.rb +100 -0
  59. data/lib/templates/javascript_notifier.erb +15 -0
  60. data/lib/templates/rescue.erb +91 -0
  61. data/rails/init.rb +1 -0
  62. data/resources/README.md +34 -0
  63. data/resources/ca-bundle.crt +3376 -0
  64. data/script/integration_test.rb +38 -0
  65. data/test/backtrace_test.rb +162 -0
  66. data/test/capistrano_test.rb +34 -0
  67. data/test/catcher_test.rb +333 -0
  68. data/test/cloudtrapper_2_2.xsd +78 -0
  69. data/test/cloudtrapper_tasks_test.rb +170 -0
  70. data/test/configuration_test.rb +221 -0
  71. data/test/helper.rb +263 -0
  72. data/test/javascript_notifier_test.rb +52 -0
  73. data/test/logger_test.rb +73 -0
  74. data/test/notice_test.rb +468 -0
  75. data/test/notifier_test.rb +246 -0
  76. data/test/rack_test.rb +58 -0
  77. data/test/rails_initializer_test.rb +36 -0
  78. data/test/recursion_test.rb +10 -0
  79. data/test/sender_test.rb +261 -0
  80. data/test/user_informer_test.rb +29 -0
  81. metadata +301 -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 'cloudtrapper'
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 ||= "api.cloudtrapper.io"
18
+
19
+ secure = (ARGV[2] == "secure")
20
+
21
+ exception = begin
22
+ raise "Testing cloudtrapper notifier with secure = #{secure}. If you can see this, it works."
23
+ rescue => foo
24
+ foo
25
+ end
26
+
27
+ Cloudtrapper.configure do |config|
28
+ config.secure = secure
29
+ config.host = host
30
+ config.api_key = ARGV.first
31
+ end
32
+ puts "Configuration:"
33
+ Cloudtrapper.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
+ Cloudtrapper.notify(exception)
38
+
@@ -0,0 +1,162 @@
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 = Cloudtrapper::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 = Cloudtrapper::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 Cloudtrapper::Backtrace.parse(one), Cloudtrapper::Backtrace.parse(two)
48
+ end
49
+
50
+ should "parse massive one-line exceptions into multiple lines" do
51
+ original_backtrace = Cloudtrapper::Backtrace.
52
+ parse(["one:1:in `one'\n two:2:in `two'\n three:3:in `three`"])
53
+ expected_backtrace = Cloudtrapper::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
+ Cloudtrapper.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 = Cloudtrapper::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 = Cloudtrapper::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
+ Cloudtrapper.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 = Cloudtrapper::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 = Cloudtrapper::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
+ Cloudtrapper.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
+ Cloudtrapper::Backtrace.parse(backtrace, :filters => default_filters)
126
+
127
+ backtrace_without_root =
128
+ Cloudtrapper::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/cloudtrapper.rb:13:in `voodoo`']
136
+ outside_notifier = ['users_controller:8:in `index`']
137
+
138
+ without_inside = Cloudtrapper::Backtrace.parse(outside_notifier)
139
+ with_inside = Cloudtrapper::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 = Cloudtrapper::Backtrace.parse(["foo:13:in `one'", "baz:14:in `two'"],
148
+ :filters => filters)
149
+ expected = Cloudtrapper::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
+ Cloudtrapper::Configuration::DEFAULT_BACKTRACE_FILTERS
160
+ end
161
+
162
+ end
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ require 'capistrano/configuration'
4
+ require 'cloudtrapper/capistrano'
5
+
6
+ class CapistranoTest < Test::Unit::TestCase
7
+ def setup
8
+ super
9
+ reset_config
10
+
11
+ @configuration = Capistrano::Configuration.new
12
+ Cloudtrapper::Capistrano.load_into(@configuration)
13
+ @configuration.dry_run = true
14
+ end
15
+
16
+ should "define cloudtrapper:deploy task" do
17
+ assert_not_nil @configuration.find_task('cloudtrapper:deploy')
18
+ end
19
+
20
+ should "log when calling cloudtrapper: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('cloudtrapper:deploy')
30
+
31
+ assert io.string.include?('** Notifying Cloudtrapper of Deploy')
32
+ assert io.string.include?('** Cloudtrapper Notification Complete')
33
+ end
34
+ end
@@ -0,0 +1,333 @@
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
+ Cloudtrapper.sender = CollectingSender.new
11
+ define_constant('RAILS_ROOT', '/path/to/rails/root')
12
+ end
13
+
14
+ def ignore(exception_class)
15
+ Cloudtrapper.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, Cloudtrapper::Rails::ActionControllerCatcher)
21
+ klass.class_eval(&definition) if definition
22
+ define_constant('CloudtrapperTestController', klass)
23
+ end
24
+ end
25
+
26
+ def assert_sent_hash(hash, xpath)
27
+ hash.each do |key, value|
28
+ next if key.match(/^cloudtrapper\./) # 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
+ Cloudtrapper.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_cloudtrapper)
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_cloudtrapper(: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_cloudtrapper)
185
+ end
186
+
187
+ should "not create actions from Cloudtrapper methods" do
188
+ controller = build_controller_class.new
189
+ assert_equal [], Cloudtrapper::Rails::ActionControllerCatcher.instance_methods
190
+ end
191
+
192
+ should "ignore exceptions when user agent is being ignored by regular expression" do
193
+ Cloudtrapper.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
+ Cloudtrapper.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
+ Cloudtrapper.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' => "cloudtrapper_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' => "cloudtrapper_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' => "cloudtrapper_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' => "cloudtrapper_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
+ Cloudtrapper.configuration.development_lookup = true
270
+ Cloudtrapper.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 Cloudtrapper.configuration.host.to_json, @response.body
283
+ assert_match Cloudtrapper.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
+ Cloudtrapper.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
@@ -0,0 +1,78 @@
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:element name="app-version" type="xs:string" minOccurs="0"/>
74
+ <xs:element name="hostname" type="xs:string" minOccurs="0"/>
75
+ </xs:sequence>
76
+ </xs:complexType>
77
+
78
+ </xs:schema>