airbrake 3.1.6 → 3.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/CHANGELOG +116 -0
  2. data/LICENSE +61 -0
  3. data/README.md +28 -478
  4. data/README_FOR_HEROKU_ADDON.md +13 -5
  5. data/Rakefile +26 -103
  6. data/TESTED_AGAINST +6 -0
  7. data/airbrake.gemspec +13 -14
  8. data/features/metal.feature +27 -12
  9. data/features/rack.feature +59 -59
  10. data/features/rails.feature +79 -100
  11. data/features/rails_with_js_notifier.feature +9 -21
  12. data/features/rake.feature +6 -4
  13. data/features/sinatra.feature +32 -6
  14. data/features/step_definitions/file_steps.rb +4 -0
  15. data/features/step_definitions/rack_steps.rb +9 -5
  16. data/features/step_definitions/rails_application_steps.rb +46 -278
  17. data/features/step_definitions/rake_steps.rb +16 -11
  18. data/features/support/airbrake_shim.rb.template +0 -2
  19. data/features/support/aruba.rb +5 -0
  20. data/features/support/env.rb +12 -6
  21. data/features/support/rails.rb +30 -73
  22. data/features/support/rake/Rakefile +1 -2
  23. data/features/user_informer.feature +12 -19
  24. data/generators/airbrake/airbrake_generator.rb +1 -1
  25. data/lib/airbrake.rb +9 -8
  26. data/lib/airbrake/cli/project_factory.rb +6 -3
  27. data/lib/airbrake/configuration.rb +7 -0
  28. data/lib/airbrake/notice.rb +52 -3
  29. data/lib/airbrake/rack.rb +16 -7
  30. data/lib/airbrake/rails/action_controller_catcher.rb +5 -3
  31. data/lib/airbrake/rails/controller_methods.rb +6 -6
  32. data/lib/airbrake/rails/error_lookup.rb +4 -2
  33. data/lib/airbrake/rails/javascript_notifier.rb +7 -3
  34. data/lib/airbrake/rails/middleware.rb +65 -0
  35. data/lib/airbrake/rails3_tasks.rb +16 -21
  36. data/lib/airbrake/railtie.rb +9 -16
  37. data/lib/airbrake/rake_handler.rb +2 -2
  38. data/lib/airbrake/sender.rb +57 -6
  39. data/lib/airbrake/shared_tasks.rb +24 -11
  40. data/lib/airbrake/sinatra.rb +34 -0
  41. data/lib/airbrake/user_informer.rb +9 -0
  42. data/lib/airbrake/version.rb +1 -1
  43. data/lib/rails/generators/airbrake/airbrake_generator.rb +10 -10
  44. data/{test/airbrake_2_3.xsd → resources/airbrake_2_4.xsd} +1 -0
  45. data/resources/airbrake_3_0.json +52 -0
  46. data/test/catcher_test.rb +198 -240
  47. data/test/configuration_test.rb +2 -0
  48. data/test/helper.rb +123 -53
  49. data/test/notice_test.rb +45 -1
  50. data/test/sender_test.rb +63 -32
  51. metadata +60 -79
  52. data/MIT-LICENSE +0 -22
  53. data/SUPPORTED_RAILS_VERSIONS +0 -38
  54. data/TESTING.md +0 -41
  55. data/features/step_definitions/metal_steps.rb +0 -23
  56. data/features/support/terminal.rb +0 -107
  57. data/lib/airbrake/extensions/blank.rb +0 -73
  58. data/lib/airbrake/rails/middleware/exceptions_catcher.rb +0 -33
@@ -31,6 +31,7 @@ class ConfigurationTest < Test::Unit::TestCase
31
31
  assert_config_default :development_lookup, true
32
32
  assert_config_default :framework, 'Standalone'
33
33
  assert_config_default :async, nil
34
+ assert_config_default :project_id, nil
34
35
  end
35
36
 
36
37
  should "set GirlFriday-callable for async=true" do
@@ -86,6 +87,7 @@ class ConfigurationTest < Test::Unit::TestCase
86
87
  assert_config_overridable :development_lookup
87
88
  assert_config_overridable :logger
88
89
  assert_config_overridable :async
90
+ assert_config_overridable :project_id
89
91
  end
90
92
 
91
93
  should "have an api key" do
data/test/helper.rb CHANGED
@@ -1,5 +1,3 @@
1
- require 'simplecov'
2
- SimpleCov.start
3
1
  require 'test/unit'
4
2
  require 'rubygems'
5
3
 
@@ -10,19 +8,140 @@ require 'thread'
10
8
  require 'shoulda'
11
9
  require 'mocha'
12
10
 
11
+ require 'abstract_controller'
13
12
  require 'action_controller'
14
- require 'action_controller/test_process'
13
+ require 'action_dispatch'
14
+ require 'active_support/dependencies'
15
+ require 'active_model'
15
16
  require 'active_record'
16
- require 'active_support'
17
+ require 'active_support/core_ext/kernel/reporting'
18
+
17
19
  require 'nokogiri'
18
20
  require 'rack'
19
21
  require 'bourne'
20
22
  require 'sham_rack'
23
+ require 'json-schema'
21
24
 
22
25
  require "airbrake"
23
26
 
24
27
  begin require 'redgreen'; rescue LoadError; end
25
28
 
29
+ # Show backtraces for deprecated behavior for quicker cleanup.
30
+ ActiveSupport::Deprecation.debug = true
31
+
32
+ FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
33
+ FIXTURES = Pathname.new(FIXTURE_LOAD_PATH)
34
+
35
+ SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
36
+
37
+ class RoutedRackApp
38
+ attr_reader :routes
39
+
40
+ def initialize(routes, &blk)
41
+ @routes = routes
42
+ @stack = ActionDispatch::MiddlewareStack.new(&blk).build(@routes)
43
+ end
44
+
45
+ def call(env)
46
+ @stack.call(env)
47
+ end
48
+ end
49
+
50
+ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
51
+ setup do
52
+ @routes = SharedTestRoutes
53
+ end
54
+ end
55
+
56
+ class ActionController::IntegrationTest < ActiveSupport::TestCase
57
+ def self.build_app(routes = nil)
58
+ RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
59
+ middleware.use "ActionDispatch::Callbacks"
60
+ middleware.use "ActionDispatch::ParamsParser"
61
+ middleware.use "ActionDispatch::Cookies"
62
+ middleware.use "ActionDispatch::Flash"
63
+ middleware.use "ActionDispatch::Head"
64
+ yield(middleware) if block_given?
65
+ end
66
+ end
67
+
68
+ self.app = build_app
69
+
70
+ # Stub Rails dispatcher so it does not get controller references and
71
+ # simply return the controller#action as Rack::Body.
72
+ class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher
73
+ protected
74
+ def controller_reference(controller_param)
75
+ controller_param
76
+ end
77
+
78
+ def dispatch(controller, action, env)
79
+ [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]]
80
+ end
81
+ end
82
+
83
+ def self.stub_controllers
84
+ old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher
85
+ ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
86
+ ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher }
87
+ yield ActionDispatch::Routing::RouteSet.new
88
+ ensure
89
+ ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
90
+ ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher }
91
+ end
92
+
93
+ def with_routing(&block)
94
+ temporary_routes = ActionDispatch::Routing::RouteSet.new
95
+ old_app, self.class.app = self.class.app, self.class.build_app(temporary_routes)
96
+ old_routes = SharedTestRoutes
97
+ silence_warnings { Object.const_set(:SharedTestRoutes, temporary_routes) }
98
+
99
+ yield temporary_routes
100
+ ensure
101
+ self.class.app = old_app
102
+ silence_warnings { Object.const_set(:SharedTestRoutes, old_routes) }
103
+ end
104
+
105
+ def with_autoload_path(path)
106
+ path = File.join(File.dirname(__FILE__), "fixtures", path)
107
+ if ActiveSupport::Dependencies.autoload_paths.include?(path)
108
+ yield
109
+ else
110
+ begin
111
+ ActiveSupport::Dependencies.autoload_paths << path
112
+ yield
113
+ ensure
114
+ ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path}
115
+ ActiveSupport::Dependencies.clear
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ module ActionController
122
+ class Base
123
+ include ActionController::Testing
124
+ end
125
+
126
+ Base.view_paths = FIXTURE_LOAD_PATH
127
+
128
+ class TestCase
129
+ include ActionDispatch::TestProcess
130
+
131
+ setup do
132
+ @routes = SharedTestRoutes
133
+ end
134
+ end
135
+ end
136
+
137
+ # This stub emulates the Railtie including the URL helpers from a Rails application
138
+ module ActionController
139
+ class Base
140
+ include SharedTestRoutes.url_helpers
141
+ end
142
+ end
143
+
144
+
26
145
  module TestMethods
27
146
  def rescue_action e
28
147
  raise e
@@ -56,41 +175,6 @@ module TestMethods
56
175
  end
57
176
 
58
177
  class Test::Unit::TestCase
59
- def request(action = nil, method = :get, user_agent = nil, params = {})
60
- @request = ActionController::TestRequest.new
61
- @request.action = action ? action.to_s : ""
62
-
63
- if user_agent
64
- if @request.respond_to?(:user_agent=)
65
- @request.user_agent = user_agent
66
- else
67
- @request.env["HTTP_USER_AGENT"] = user_agent
68
- end
69
- end
70
- @request.query_parameters = @request.query_parameters.merge(params)
71
- @response = ActionController::TestResponse.new
72
- @controller.process(@request, @response)
73
- end
74
-
75
- # Borrowed from ActiveSupport 2.3.2
76
- def assert_difference(expression, difference = 1, message = nil, &block)
77
- b = block.send(:binding)
78
- exps = Array.wrap(expression)
79
- before = exps.map { |e| eval(e, b) }
80
-
81
- yield
82
-
83
- exps.each_with_index do |e, i|
84
- error = "#{e.inspect} didn't change by #{difference}"
85
- error = "#{message}.\n#{error}" if message
86
- assert_equal(before[i] + difference, eval(e, b), error)
87
- end
88
- end
89
-
90
- def assert_no_difference(expression, message = nil, &block)
91
- assert_difference expression, 0, message, &block
92
- end
93
-
94
178
  def stub_sender
95
179
  stub('sender', :send_to_airbrake => nil)
96
180
  end
@@ -109,10 +193,6 @@ class Test::Unit::TestCase
109
193
  end
110
194
  end
111
195
 
112
- def create_dummy
113
- Airbrake::DummySender.new
114
- end
115
-
116
196
  def reset_config
117
197
  Airbrake.configuration = nil
118
198
  Airbrake.configure do |config|
@@ -187,8 +267,6 @@ class Test::Unit::TestCase
187
267
  expect.with {|actual| actual =~ expected }.never
188
268
  end
189
269
  end
190
-
191
-
192
270
  end
193
271
 
194
272
  module DefinesConstants
@@ -241,14 +319,6 @@ class CollectingSender
241
319
  end
242
320
  end
243
321
 
244
- class FakeLogger
245
- def info(*args); end
246
- def debug(*args); end
247
- def warn(*args); end
248
- def error(*args); end
249
- def fatal(*args); end
250
- end
251
-
252
322
  class BacktracedException < Exception
253
323
  attr_accessor :backtrace
254
324
  def initialize(opts)
data/test/notice_test.rb CHANGED
@@ -61,12 +61,18 @@ class NoticeTest < Test::Unit::TestCase
61
61
  end
62
62
 
63
63
  def assert_valid_notice_document(document)
64
- xsd_path = File.join(File.dirname(__FILE__), "airbrake_2_3.xsd")
64
+ xsd_path = File.expand_path(File.join(File.dirname(__FILE__),"..", "resources", "airbrake_2_4.xsd"))
65
65
  schema = Nokogiri::XML::Schema.new(IO.read(xsd_path))
66
66
  errors = schema.validate(document)
67
67
  assert errors.empty?, errors.collect{|e| e.message }.join
68
68
  end
69
69
 
70
+ def assert_valid_json(notice)
71
+ json_schema = File.expand_path(File.join(File.dirname(__FILE__),"..", "resources", "airbrake_3_0.json"))
72
+ errors = JSON::Validator.fully_validate(json_schema, notice)
73
+ assert errors.empty?, errors.join
74
+ end
75
+
70
76
  def assert_filters_hash(attribute)
71
77
  filters = ["abc", :def]
72
78
  original = { 'abc' => "123", 'def' => "456", 'ghi' => "789", 'nested' => { 'abc' => '100' },
@@ -251,6 +257,15 @@ class NoticeTest < Test::Unit::TestCase
251
257
  should "filter session" do
252
258
  assert_filters_hash(:session_data)
253
259
  end
260
+
261
+ should "should always remove a Rails application's secret token" do
262
+ original = {
263
+ "action_dispatch.secret_token" => "abc123xyz456",
264
+ "abc" => "123"
265
+ }
266
+ notice = build_notice(:cgi_data => original)
267
+ assert_equal({"abc" => "123"}, notice.cgi_data)
268
+ end
254
269
 
255
270
  should "remove rack.request.form_vars" do
256
271
  original = {
@@ -262,6 +277,34 @@ class NoticeTest < Test::Unit::TestCase
262
277
  assert_equal({"abc" => "123"}, notice.cgi_data)
263
278
  end
264
279
 
280
+ context "a Notice turned into JSON" do
281
+ setup do
282
+ @exception = build_exception
283
+
284
+ @notice = build_notice({
285
+ :notifier_name => 'a name',
286
+ :notifier_version => '1.2.3',
287
+ :notifier_url => 'http://some.url/path',
288
+ :exception => @exception,
289
+ :controller => "controller",
290
+ :action => "action",
291
+ :url => "http://url.com",
292
+ :parameters => { "paramskey" => "paramsvalue",
293
+ "nestparentkey" => { "nestkey" => "nestvalue" } },
294
+ :session_data => { "sessionkey" => "sessionvalue" },
295
+ :cgi_data => { "cgikey" => "cgivalue" },
296
+ :project_root => "RAILS_ROOT",
297
+ :environment_name => "RAILS_ENV"
298
+ })
299
+
300
+ @json = @notice.to_json
301
+ end
302
+
303
+ should "validate against the JSON schema" do
304
+ assert_valid_json @json
305
+ end
306
+ end
307
+
265
308
  context "a Notice turned into XML" do
266
309
  setup do
267
310
  Airbrake.configure do |config|
@@ -295,6 +338,7 @@ class NoticeTest < Test::Unit::TestCase
295
338
  assert_valid_notice_document @document
296
339
  end
297
340
 
341
+
298
342
  should "serialize a Notice to XML when sent #to_xml" do
299
343
  assert_valid_node(@document, "//api-key", @notice.api_key)
300
344
 
data/test/sender_test.rb CHANGED
@@ -31,32 +31,6 @@ class SenderTest < Test::Unit::TestCase
31
31
  http
32
32
  end
33
33
 
34
- should "post to Airbrake with XML passed" do
35
- xml_notice = Airbrake::Notice.new(:error_class => "FooBar", :error_message => "Foo Bar").to_xml
36
-
37
- http = stub_http
38
-
39
- sender = build_sender
40
- sender.send_to_airbrake(xml_notice)
41
-
42
- assert_received(http, :post) do |expect|
43
- expect.with(anything, xml_notice, Airbrake::HEADERS)
44
- end
45
- end
46
-
47
- should "post to Airbrake with a Notice instance passed" do
48
- notice = Airbrake::Notice.new(:error_class => "FooBar", :error_message => "Foo Bar")
49
-
50
- http = stub_http
51
-
52
- sender = build_sender
53
- sender.send_to_airbrake(notice)
54
-
55
- assert_received(http, :post) do |expect|
56
- expect.with(anything, notice.to_xml, Airbrake::HEADERS)
57
- end
58
- end
59
-
60
34
  should "post to Airbrake when using an HTTP proxy" do
61
35
  response = stub(:body => 'body')
62
36
  http = stub(:post => response,
@@ -79,7 +53,7 @@ class SenderTest < Test::Unit::TestCase
79
53
  :proxy_user => proxy_user,
80
54
  :proxy_pass => proxy_pass)
81
55
  assert_received(http, :post) do |expect|
82
- expect.with(uri.path, anything, Airbrake::HEADERS)
56
+ expect.with(uri.path, anything, Airbrake::Sender::HEADERS[:xml])
83
57
  end
84
58
  assert_received(Net::HTTP, :Proxy) do |expect|
85
59
  expect.with(proxy_host, proxy_port, proxy_user, proxy_pass)
@@ -91,6 +65,66 @@ class SenderTest < Test::Unit::TestCase
91
65
  assert_equal "3799307", send_exception(:secure => false)
92
66
  end
93
67
 
68
+ context "when using the XML API" do
69
+
70
+ should "post to Airbrake with XML passed" do
71
+ xml_notice = Airbrake::Notice.new(:error_class => "FooBar", :error_message => "Foo Bar").to_xml
72
+
73
+ http = stub_http
74
+
75
+ sender = build_sender
76
+ sender.send_to_airbrake(xml_notice)
77
+
78
+ assert_received(http, :post) do |expect|
79
+ expect.with(anything, xml_notice, Airbrake::Sender::HEADERS[:xml])
80
+ end
81
+ end
82
+
83
+ should "post to Airbrake with a Notice instance passed" do
84
+ notice = Airbrake::Notice.new(:error_class => "FooBar", :error_message => "Foo Bar")
85
+
86
+ http = stub_http
87
+
88
+ sender = build_sender
89
+ sender.send_to_airbrake(notice)
90
+
91
+ assert_received(http, :post) do |expect|
92
+ expect.with(anything, notice.to_xml, Airbrake::Sender::HEADERS[:xml])
93
+ end
94
+ end
95
+
96
+ end
97
+
98
+ context "when using new JSON API" do
99
+ should "post to Airbrake with JSON passed" do
100
+ json_notice = Airbrake::Notice.new(:error_class => "FooBar", :error_message => "Foo Bar").to_json
101
+
102
+ http = stub_http
103
+
104
+ sender = build_sender(:project_id => "PROJECT_ID", :host => "collect.airbrake.io")
105
+ sender.send_to_airbrake(json_notice)
106
+
107
+ assert_received(http, :post) do |expect|
108
+ expect.with(anything, json_notice, Airbrake::Sender::HEADERS[:json])
109
+ end
110
+
111
+ end
112
+
113
+ should "post to Airbrake with notice passed" do
114
+ notice = Airbrake::Notice.new(:error_class => "FooBar", :error_message => "Foo Bar")
115
+
116
+ http = stub_http
117
+
118
+ sender = build_sender(:project_id => "PROJECT_ID", :host => "collect.airbrake.io")
119
+ sender.send_to_airbrake(notice)
120
+
121
+ assert_received(http, :post) do |expect|
122
+ expect.with(anything, notice.to_json, Airbrake::Sender::HEADERS[:json])
123
+ end
124
+
125
+ end
126
+ end
127
+
94
128
  context "when encountering exceptions: " do
95
129
  context "HTTP connection setup problems" do
96
130
  should "not be rescued" do
@@ -176,13 +210,13 @@ class SenderTest < Test::Unit::TestCase
176
210
  url = "http://api.airbrake.io:80#{Airbrake::Sender::NOTICES_URI}"
177
211
  uri = URI.parse(url)
178
212
  send_exception(:secure => false)
179
- assert_received(http, :post) {|expect| expect.with(uri.path, anything, Airbrake::HEADERS) }
213
+ assert_received(http, :post) {|expect| expect.with(uri.path, anything, Airbrake::Sender::HEADERS[:xml]) }
180
214
  end
181
215
 
182
216
  should "post to the right path for ssl" do
183
217
  http = stub_http
184
218
  send_exception(:secure => true)
185
- assert_received(http, :post) {|expect| expect.with(Airbrake::Sender::NOTICES_URI, anything, Airbrake::HEADERS) }
219
+ assert_received(http, :post) {|expect| expect.with(Airbrake::Sender::NOTICES_URI, anything, Airbrake::Sender::HEADERS[:xml]) }
186
220
  end
187
221
 
188
222
  should "verify the SSL peer when the use_ssl option is set to true" do
@@ -255,8 +289,6 @@ class SenderTest < Test::Unit::TestCase
255
289
  send_exception(:secure => false, :host => 'example.org')
256
290
  assert_received(Net::HTTP, :new) {|expect| expect.with('example.org', 80) }
257
291
  end
258
-
259
-
260
292
  end
261
293
 
262
294
  context "network timeouts" do
@@ -284,5 +316,4 @@ class SenderTest < Test::Unit::TestCase
284
316
  assert_received(http, :read_timeout=) {|expect| expect.with(10) }
285
317
  end
286
318
  end
287
-
288
319
  end