bugsnag-maglev- 2.8.12

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 (65) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +52 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +15 -0
  6. data/CHANGELOG.md +425 -0
  7. data/CONTRIBUTING.md +43 -0
  8. data/Gemfile +2 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.md +804 -0
  11. data/Rakefile +29 -0
  12. data/VERSION +1 -0
  13. data/bugsnag.gemspec +32 -0
  14. data/lib/bugsnag.rb +129 -0
  15. data/lib/bugsnag/capistrano.rb +7 -0
  16. data/lib/bugsnag/capistrano2.rb +32 -0
  17. data/lib/bugsnag/configuration.rb +129 -0
  18. data/lib/bugsnag/delay/resque.rb +21 -0
  19. data/lib/bugsnag/delayed_job.rb +57 -0
  20. data/lib/bugsnag/delivery.rb +18 -0
  21. data/lib/bugsnag/delivery/synchronous.rb +51 -0
  22. data/lib/bugsnag/delivery/thread_queue.rb +53 -0
  23. data/lib/bugsnag/deploy.rb +35 -0
  24. data/lib/bugsnag/helpers.rb +127 -0
  25. data/lib/bugsnag/mailman.rb +28 -0
  26. data/lib/bugsnag/meta_data.rb +7 -0
  27. data/lib/bugsnag/middleware/callbacks.rb +19 -0
  28. data/lib/bugsnag/middleware/mailman.rb +13 -0
  29. data/lib/bugsnag/middleware/rack_request.rb +72 -0
  30. data/lib/bugsnag/middleware/rails2_request.rb +52 -0
  31. data/lib/bugsnag/middleware/rails3_request.rb +42 -0
  32. data/lib/bugsnag/middleware/rake.rb +23 -0
  33. data/lib/bugsnag/middleware/sidekiq.rb +13 -0
  34. data/lib/bugsnag/middleware/warden_user.rb +39 -0
  35. data/lib/bugsnag/middleware_stack.rb +98 -0
  36. data/lib/bugsnag/notification.rb +452 -0
  37. data/lib/bugsnag/rack.rb +53 -0
  38. data/lib/bugsnag/rails.rb +66 -0
  39. data/lib/bugsnag/rails/action_controller_rescue.rb +62 -0
  40. data/lib/bugsnag/rails/active_record_rescue.rb +20 -0
  41. data/lib/bugsnag/rails/controller_methods.rb +44 -0
  42. data/lib/bugsnag/railtie.rb +78 -0
  43. data/lib/bugsnag/rake.rb +25 -0
  44. data/lib/bugsnag/resque.rb +40 -0
  45. data/lib/bugsnag/sidekiq.rb +38 -0
  46. data/lib/bugsnag/tasks.rb +3 -0
  47. data/lib/bugsnag/tasks/bugsnag.cap +48 -0
  48. data/lib/bugsnag/tasks/bugsnag.rake +89 -0
  49. data/lib/bugsnag/version.rb +3 -0
  50. data/lib/generators/bugsnag/bugsnag_generator.rb +24 -0
  51. data/rails/init.rb +3 -0
  52. data/spec/code_spec.rb +86 -0
  53. data/spec/fixtures/crashes/end_of_file.rb +9 -0
  54. data/spec/fixtures/crashes/short_file.rb +1 -0
  55. data/spec/fixtures/crashes/start_of_file.rb +9 -0
  56. data/spec/fixtures/middleware/internal_info_setter.rb +11 -0
  57. data/spec/fixtures/middleware/public_info_setter.rb +11 -0
  58. data/spec/fixtures/tasks/Rakefile +15 -0
  59. data/spec/helper_spec.rb +144 -0
  60. data/spec/integration_spec.rb +110 -0
  61. data/spec/middleware_spec.rb +181 -0
  62. data/spec/notification_spec.rb +822 -0
  63. data/spec/rack_spec.rb +56 -0
  64. data/spec/spec_helper.rb +53 -0
  65. metadata +198 -0
@@ -0,0 +1,181 @@
1
+ require 'spec_helper'
2
+ require 'fixtures/middleware/public_info_setter'
3
+ require 'fixtures/middleware/internal_info_setter'
4
+
5
+ describe Bugsnag::MiddlewareStack do
6
+ it "runs before_bugsnag_notify callbacks, adding a tab" do
7
+ callback_run_count = 0
8
+ Bugsnag.before_notify_callbacks << lambda {|notif|
9
+ notif.add_tab(:some_tab, {
10
+ :info => "here",
11
+ :data => "also here"
12
+ })
13
+ callback_run_count += 1
14
+ }
15
+
16
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
17
+ expect(callback_run_count).to eq(1)
18
+
19
+ expect(Bugsnag).to have_sent_notification{ |payload|
20
+ event = get_event_from_payload(payload)
21
+ expect(event["metaData"]["some_tab"]).not_to be_nil
22
+ expect(event["metaData"]["some_tab"]["info"]).to eq("here")
23
+ expect(event["metaData"]["some_tab"]["data"]).to eq("also here")
24
+ }
25
+
26
+ end
27
+
28
+ it "runs before_bugsnag_notify callbacks, adding custom data" do
29
+ callback_run_count = 0
30
+ Bugsnag.before_notify_callbacks << lambda {|notif|
31
+ notif.add_custom_data(:info, "here")
32
+ notif.add_custom_data(:data, "also here")
33
+
34
+ callback_run_count += 1
35
+ }
36
+
37
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
38
+ expect(callback_run_count).to eq(1)
39
+
40
+ expect(Bugsnag).to have_sent_notification{ |payload|
41
+ event = get_event_from_payload(payload)
42
+ expect(event["metaData"]["custom"]).not_to be_nil
43
+ expect(event["metaData"]["custom"]["info"]).to eq("here")
44
+ expect(event["metaData"]["custom"]["data"]).to eq("also here")
45
+ }
46
+
47
+ end
48
+
49
+ it "runs before_bugsnag_notify callbacks, setting the user" do
50
+ callback_run_count = 0
51
+ Bugsnag.before_notify_callbacks << lambda {|notif|
52
+ notif.user = {:id => "here", :email => "also here", :name => "also here too", :random_key => "also here too too"}
53
+ callback_run_count += 1
54
+ }
55
+
56
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
57
+ expect(callback_run_count).to eq(1)
58
+
59
+ expect(Bugsnag).to have_sent_notification{ |payload|
60
+ event = get_event_from_payload(payload)
61
+ expect(event["user"]).not_to be_nil
62
+ expect(event["user"]["id"]).to eq("here")
63
+ expect(event["user"]["email"]).to eq("also here")
64
+ expect(event["user"]["name"]).to eq("also here too")
65
+ expect(event["user"]["random_key"]).to eq("also here too too")
66
+ }
67
+
68
+ end
69
+
70
+ it "allows overrides to override values set by internal middleware" do
71
+ Bugsnag.configuration.internal_middleware.use(InternalInfoSetter)
72
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {:info => "overridden"})
73
+
74
+ expect(Bugsnag).to have_sent_notification{ |payload|
75
+ event = get_event_from_payload(payload)
76
+ expect(event["metaData"]["custom"]).not_to be_nil
77
+ expect(event["metaData"]["custom"]["info"]).not_to eq(InternalInfoSetter::MESSAGE)
78
+ expect(event["metaData"]["custom"]["info"]).to eq("overridden")
79
+ }
80
+ end
81
+
82
+ it "doesn't allow overrides to override public middleware" do
83
+ Bugsnag.configuration.middleware.use(PublicInfoSetter)
84
+
85
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {:info => "overridden"})
86
+
87
+ expect(Bugsnag).to have_sent_notification{ |payload|
88
+ event = get_event_from_payload(payload)
89
+ expect(event["metaData"]["custom"]).not_to be_nil
90
+ expect(event["metaData"]["custom"]["info"]).not_to eq("overridden")
91
+ expect(event["metaData"]["custom"]["info"]).to eq(PublicInfoSetter::MESSAGE)
92
+ }
93
+ end
94
+
95
+ it "does not have have before or after callbacks by default" do
96
+ expect(Bugsnag.before_notify_callbacks.size).to eq(0)
97
+ expect(Bugsnag.after_notify_callbacks.size).to eq(0)
98
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
99
+ expect(Bugsnag).to have_sent_notification{ |payload|
100
+ event = get_event_from_payload(payload)
101
+ expect(event["metaData"].size).to eq(0)
102
+ }
103
+ end
104
+
105
+ it "runs after_bugsnag_notify callbacks" do
106
+ callback_run_count = 0
107
+ Bugsnag.after_notify_callbacks << lambda {|notif|
108
+ callback_run_count += 1
109
+ }
110
+
111
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
112
+
113
+ expect(callback_run_count).to eq(1)
114
+ expect(Bugsnag::Notification).to have_sent_notification { }
115
+ end
116
+
117
+ it "does not execute disabled bugsnag middleware" do
118
+ callback_run_count = 0
119
+ Bugsnag.configure do |config|
120
+ config.middleware.disable(Bugsnag::Middleware::Callbacks)
121
+ end
122
+
123
+ Bugsnag.before_notify_callbacks << lambda {|notif|
124
+ callback_run_count += 1
125
+ }
126
+
127
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
128
+ expect(callback_run_count).to eq(0)
129
+ end
130
+
131
+ it "does not notify if a callback told so" do
132
+ Bugsnag.before_notify_callbacks << lambda do |notif|
133
+ notif.ignore!
134
+ end
135
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
136
+ expect(Bugsnag::Notification).not_to have_sent_notification { }
137
+ end
138
+
139
+ it "allows inspection of meta_data before ignoring exception" do
140
+ # Use before notify callbacks as only the callback based metadata is
141
+ # available to before_notify_callbacks
142
+ Bugsnag.before_notify_callbacks << lambda do |notif|
143
+ notif.add_tab(:sidekiq, {:retry_count => 4})
144
+ end
145
+
146
+ Bugsnag.before_notify_callbacks << lambda do |notif|
147
+ notif.ignore! if notif.meta_data[:sidekiq][:retry_count] > 3
148
+ end
149
+
150
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
151
+ expect(Bugsnag::Notification).not_to have_sent_notification
152
+
153
+ end
154
+
155
+ it "allows meta_data to be modified in a middleware" do
156
+ MetaDataMunger = Class.new do
157
+ def initialize(bugsnag)
158
+ @bugsnag = bugsnag
159
+ end
160
+
161
+ def call(notification)
162
+ token = notification.meta_data[:sidekiq][:args].first
163
+ notification.meta_data[:sidekiq][:args] = ["#{token[0...6]}*****#{token[-4..-1]}"]
164
+ @bugsnag.call(notification)
165
+ end
166
+ end
167
+
168
+ Bugsnag.configure do |c|
169
+ c.middleware.use MetaDataMunger
170
+ end
171
+
172
+ notification = Bugsnag.notify(BugsnagTestException.new("It crashed"), {
173
+ :sidekiq => {
174
+ :args => ["abcdef123456abcdef123456abcdef123456"]
175
+ }
176
+ })
177
+
178
+ expect(notification.meta_data[:sidekiq][:args]).to eq(["abcdef*****3456"])
179
+ end
180
+
181
+ end
@@ -0,0 +1,822 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'securerandom'
4
+ require 'ostruct'
5
+
6
+ module ActiveRecord; class RecordNotFound < RuntimeError; end; end
7
+ class NestedException < StandardError; attr_accessor :original_exception; end
8
+ class BugsnagTestExceptionWithMetaData < Exception; include Bugsnag::MetaData; end
9
+ class BugsnagSubclassTestException < BugsnagTestException; end
10
+
11
+ class Ruby21Exception < RuntimeError
12
+ attr_accessor :cause
13
+ def self.raise!(msg)
14
+ e = new(msg)
15
+ e.cause = $!
16
+ raise e
17
+ end
18
+ end
19
+
20
+ class JRubyException
21
+ def self.raise!
22
+ new.gloops
23
+ end
24
+
25
+ def gloops
26
+ java.lang.System.out.printf(nil)
27
+ end
28
+ end
29
+
30
+ describe Bugsnag::Notification do
31
+ it "should contain an api_key if one is set" do
32
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
33
+
34
+ expect(Bugsnag).to have_sent_notification{ |payload|
35
+ expect(payload["apiKey"]).to eq("c9d60ae4c7e70c4b6c4ebd3e8056d2b8")
36
+ }
37
+ end
38
+
39
+ it "does not notify if api_key is not set" do
40
+ Bugsnag.configuration.api_key = nil
41
+
42
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
43
+
44
+ expect(Bugsnag).not_to have_sent_notification
45
+ end
46
+
47
+ it "does not notify if api_key is empty" do
48
+ Bugsnag.configuration.api_key = ""
49
+
50
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
51
+
52
+ expect(Bugsnag).not_to have_sent_notification
53
+ end
54
+
55
+ it "lets you override the api_key" do
56
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), :api_key => "9d84383f9be2ca94902e45c756a9979d")
57
+
58
+ expect(Bugsnag).to have_sent_notification{ |payload|
59
+ expect(payload["apiKey"]).to eq("9d84383f9be2ca94902e45c756a9979d")
60
+ }
61
+ end
62
+
63
+ it "lets you override the groupingHash" do
64
+
65
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {:grouping_hash => "this is my grouping hash"})
66
+
67
+ expect(Bugsnag).to have_sent_notification{ |payload|
68
+ event = get_event_from_payload(payload)
69
+ expect(event["groupingHash"]).to eq("this is my grouping hash")
70
+ }
71
+ end
72
+
73
+ it "uses the env variable apiKey" do
74
+ ENV["BUGSNAG_API_KEY"] = "c9d60ae4c7e70c4b6c4ebd3e8056d2b9"
75
+
76
+ Bugsnag.instance_variable_set(:@configuration, Bugsnag::Configuration.new)
77
+ Bugsnag.configure do |config|
78
+ config.release_stage = "production"
79
+ config.delivery_method = :synchronous
80
+ end
81
+
82
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
83
+
84
+ expect(Bugsnag).to have_sent_notification{ |payload|
85
+ expect(payload["apiKey"]).to eq("c9d60ae4c7e70c4b6c4ebd3e8056d2b9")
86
+ }
87
+ end
88
+
89
+ it "has the right exception class" do
90
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
91
+
92
+ expect(Bugsnag).to have_sent_notification{ |payload|
93
+ exception = get_exception_from_payload(payload)
94
+ expect(exception["errorClass"]).to eq("BugsnagTestException")
95
+ }
96
+ end
97
+
98
+ it "has the right exception message" do
99
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
100
+
101
+ expect(Bugsnag).to have_sent_notification{ |payload|
102
+ exception = get_exception_from_payload(payload)
103
+ expect(exception["message"]).to eq("It crashed")
104
+ }
105
+ end
106
+
107
+ it "has a valid stacktrace" do
108
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
109
+
110
+ expect(Bugsnag).to have_sent_notification{ |payload|
111
+ exception = get_exception_from_payload(payload)
112
+ expect(exception["stacktrace"].length).to be > 0
113
+ }
114
+ end
115
+
116
+ # TODO: nested context
117
+
118
+ it "accepts tabs in overrides and adds them to metaData" do
119
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {
120
+ :some_tab => {
121
+ :info => "here",
122
+ :data => "also here"
123
+ }
124
+ })
125
+
126
+ expect(Bugsnag).to have_sent_notification{ |payload|
127
+ event = get_event_from_payload(payload)
128
+ expect(event["metaData"]["some_tab"]).to eq(
129
+ "info" => "here",
130
+ "data" => "also here"
131
+ )
132
+ }
133
+ end
134
+
135
+ it "accepts non-hash overrides and adds them to the custom tab in metaData" do
136
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {
137
+ :info => "here",
138
+ :data => "also here"
139
+ })
140
+
141
+ expect(Bugsnag).to have_sent_notification{ |payload|
142
+ event = get_event_from_payload(payload)
143
+ expect(event["metaData"]["custom"]).to eq(
144
+ "info" => "here",
145
+ "data" => "also here"
146
+ )
147
+ }
148
+ end
149
+
150
+ it "accepts meta data from an exception that mixes in Bugsnag::MetaData" do
151
+ exception = BugsnagTestExceptionWithMetaData.new("It crashed")
152
+ exception.bugsnag_meta_data = {
153
+ :some_tab => {
154
+ :info => "here",
155
+ :data => "also here"
156
+ }
157
+ }
158
+
159
+ Bugsnag.notify(exception)
160
+
161
+ expect(Bugsnag).to have_sent_notification{ |payload|
162
+ event = get_event_from_payload(payload)
163
+ expect(event["metaData"]["some_tab"]).to eq(
164
+ "info" => "here",
165
+ "data" => "also here"
166
+ )
167
+ }
168
+ end
169
+
170
+ it "accepts meta data from an exception that mixes in Bugsnag::MetaData, but override using the overrides" do
171
+ exception = BugsnagTestExceptionWithMetaData.new("It crashed")
172
+ exception.bugsnag_meta_data = {
173
+ :some_tab => {
174
+ :info => "here",
175
+ :data => "also here"
176
+ }
177
+ }
178
+
179
+ Bugsnag.notify(exception, {:some_tab => {:info => "overridden"}})
180
+
181
+ expect(Bugsnag).to have_sent_notification{ |payload|
182
+ event = get_event_from_payload(payload)
183
+ expect(event["metaData"]["some_tab"]).to eq(
184
+ "info" => "overridden",
185
+ "data" => "also here"
186
+ )
187
+ }
188
+ end
189
+
190
+ it "accepts user_id from an exception that mixes in Bugsnag::MetaData" do
191
+ exception = BugsnagTestExceptionWithMetaData.new("It crashed")
192
+ exception.bugsnag_user_id = "exception_user_id"
193
+
194
+ Bugsnag.notify(exception)
195
+
196
+ expect(Bugsnag).to have_sent_notification{ |payload|
197
+ event = get_event_from_payload(payload)
198
+ expect(event["user"]["id"]).to eq("exception_user_id")
199
+ }
200
+ end
201
+
202
+ it "accepts user_id from an exception that mixes in Bugsnag::MetaData, but override using the overrides" do
203
+ exception = BugsnagTestExceptionWithMetaData.new("It crashed")
204
+ exception.bugsnag_user_id = "exception_user_id"
205
+
206
+ Bugsnag.notify(exception, {:user_id => "override_user_id"})
207
+
208
+ expect(Bugsnag).to have_sent_notification{ |payload|
209
+ event = get_event_from_payload(payload)
210
+ expect(event["user"]["id"]).to eq("override_user_id")
211
+ }
212
+ end
213
+
214
+ it "accepts context from an exception that mixes in Bugsnag::MetaData" do
215
+ exception = BugsnagTestExceptionWithMetaData.new("It crashed")
216
+ exception.bugsnag_context = "exception_context"
217
+
218
+ Bugsnag.notify(exception)
219
+
220
+ expect(Bugsnag).to have_sent_notification{ |payload|
221
+ event = get_event_from_payload(payload)
222
+ expect(event["context"]).to eq("exception_context")
223
+ }
224
+ end
225
+
226
+ it "accept contexts from an exception that mixes in Bugsnag::MetaData, but override using the overrides" do
227
+
228
+ exception = BugsnagTestExceptionWithMetaData.new("It crashed")
229
+ exception.bugsnag_context = "exception_context"
230
+
231
+ Bugsnag.notify(exception, {:context => "override_context"})
232
+
233
+ expect(Bugsnag).to have_sent_notification{ |payload|
234
+ event = get_event_from_payload(payload)
235
+ expect(event["context"]).to eq("override_context")
236
+ }
237
+ end
238
+
239
+ it "accepts meta_data in overrides (for backwards compatibility) and merge it into metaData" do
240
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {
241
+ :meta_data => {
242
+ :some_tab => {
243
+ :info => "here",
244
+ :data => "also here"
245
+ }
246
+ }
247
+ })
248
+
249
+ expect(Bugsnag).to have_sent_notification{ |payload|
250
+ event = get_event_from_payload(payload)
251
+ expect(event["metaData"]["some_tab"]).to eq(
252
+ "info" => "here",
253
+ "data" => "also here"
254
+ )
255
+ }
256
+ end
257
+
258
+ it "truncates large meta_data before sending" do
259
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {
260
+ :meta_data => {
261
+ :some_tab => {
262
+ :giant => SecureRandom.hex(500_000/2),
263
+ :mega => SecureRandom.hex(500_000/2)
264
+ }
265
+ }
266
+ })
267
+
268
+ expect(Bugsnag).to have_sent_notification{ |payload|
269
+ # Truncated body should be no bigger than
270
+ # 2 truncated hashes (4096*2) + rest of payload (20000)
271
+ expect(::JSON.dump(payload).length).to be < 4096*2 + 20000
272
+ }
273
+ end
274
+
275
+ it "accepts a severity in overrides" do
276
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {
277
+ :severity => "info"
278
+ })
279
+
280
+ expect(Bugsnag).to have_sent_notification{ |payload|
281
+ event = get_event_from_payload(payload)
282
+ expect(event["severity"]).to eq("info")
283
+ }
284
+
285
+ end
286
+
287
+ it "defaults to warning severity" do
288
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
289
+
290
+ expect(Bugsnag).to have_sent_notification{ |payload|
291
+ event = get_event_from_payload(payload)
292
+ expect(event["severity"]).to eq("warning")
293
+ }
294
+ end
295
+
296
+ it "does not accept a bad severity in overrides" do
297
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {
298
+ :severity => "fatal"
299
+ })
300
+
301
+ expect(Bugsnag).to have_sent_notification{ |payload|
302
+ event = get_event_from_payload(payload)
303
+ expect(event["severity"]).to eq("warning")
304
+ }
305
+ end
306
+
307
+ it "autonotifies errors" do
308
+ Bugsnag.auto_notify(BugsnagTestException.new("It crashed"))
309
+
310
+ expect(Bugsnag).to have_sent_notification{ |payload|
311
+ event = get_event_from_payload(payload)
312
+ expect(event["severity"]).to eq("error")
313
+ }
314
+ end
315
+
316
+
317
+ it "accepts a context in overrides" do
318
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {
319
+ :context => "test_context"
320
+ })
321
+
322
+ expect(Bugsnag).to have_sent_notification{ |payload|
323
+ event = get_event_from_payload(payload)
324
+ expect(event["context"]).to eq("test_context")
325
+ }
326
+ end
327
+
328
+ it "accepts a user_id in overrides" do
329
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {
330
+ :user_id => "test_user"
331
+ })
332
+
333
+ expect(Bugsnag).to have_sent_notification{ |payload|
334
+ event = get_event_from_payload(payload)
335
+ expect(event["user"]["id"]).to eq("test_user")
336
+ }
337
+ end
338
+
339
+ it "does not send a notification if auto_notify is false" do
340
+ Bugsnag.configure do |config|
341
+ config.auto_notify = false
342
+ end
343
+
344
+ Bugsnag.auto_notify(BugsnagTestException.new("It crashed"))
345
+
346
+ expect(Bugsnag).not_to have_sent_notification
347
+ end
348
+
349
+ it "contains a release_stage" do
350
+ Bugsnag.configure do |config|
351
+ config.release_stage = "production"
352
+ end
353
+
354
+ Bugsnag.auto_notify(BugsnagTestException.new("It crashed"))
355
+
356
+ expect(Bugsnag).to have_sent_notification{ |payload|
357
+ event = get_event_from_payload(payload)
358
+ expect(event["app"]["releaseStage"]).to eq("production")
359
+ }
360
+ end
361
+
362
+ it "respects the notify_release_stages setting by not sending in development" do
363
+ Bugsnag.configuration.notify_release_stages = ["production"]
364
+ Bugsnag.configuration.release_stage = "development"
365
+
366
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
367
+
368
+ expect(Bugsnag).not_to have_sent_notification
369
+ end
370
+
371
+ it "respects the notify_release_stages setting when set" do
372
+ Bugsnag.configuration.release_stage = "development"
373
+ Bugsnag.configuration.notify_release_stages = ["development"]
374
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
375
+
376
+ expect(Bugsnag).to have_sent_notification{ |payload|
377
+ event = get_event_from_payload(payload)
378
+ expect(event["exceptions"].length).to eq(1)
379
+ }
380
+ end
381
+
382
+ it "uses the https://notify.bugsnag.com endpoint by default" do
383
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
384
+
385
+ expect(WebMock).to have_requested(:post, "https://notify.bugsnag.com")
386
+ end
387
+
388
+ it "uses ssl when use_ssl is true" do
389
+ Bugsnag.configuration.use_ssl = true
390
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
391
+
392
+ expect(WebMock).to have_requested(:post, "https://notify.bugsnag.com")
393
+ end
394
+
395
+ it "does not use ssl when use_ssl is false" do
396
+ stub_request(:post, "http://notify.bugsnag.com/")
397
+ Bugsnag.configuration.use_ssl = false
398
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
399
+
400
+ expect(WebMock).to have_requested(:post, "http://notify.bugsnag.com")
401
+ end
402
+
403
+ it "uses ssl when use_ssl is unset" do
404
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
405
+
406
+ expect(WebMock).to have_requested(:post, "https://notify.bugsnag.com")
407
+ end
408
+
409
+ it "does not mark the top-most stacktrace line as inProject if out of project" do
410
+ Bugsnag.configuration.project_root = "/Random/location/here"
411
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
412
+
413
+ expect(Bugsnag).to have_sent_notification{ |payload|
414
+ exception = get_exception_from_payload(payload)
415
+ expect(exception["stacktrace"].size).to be >= 1
416
+ expect(exception["stacktrace"].first["inProject"]).to be_nil
417
+ }
418
+ end
419
+
420
+ it "marks the top-most stacktrace line as inProject if necessary" do
421
+ Bugsnag.configuration.project_root = File.expand_path File.dirname(__FILE__)
422
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
423
+
424
+ expect(Bugsnag).to have_sent_notification{ |payload|
425
+ exception = get_exception_from_payload(payload)
426
+ expect(exception["stacktrace"].size).to be >= 1
427
+ expect(exception["stacktrace"].first["inProject"]).to eq(true)
428
+ }
429
+ end
430
+
431
+ it "adds app_version to the payload if it is set" do
432
+ Bugsnag.configuration.app_version = "1.1.1"
433
+ Bugsnag.notify(BugsnagTestException.new("It crashed"))
434
+
435
+ expect(Bugsnag).to have_sent_notification{ |payload|
436
+ event = get_event_from_payload(payload)
437
+ expect(event["app"]["version"]).to eq("1.1.1")
438
+ }
439
+ end
440
+
441
+ it "filters params from all payload hashes if they are set in default params_filters" do
442
+
443
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {:request => {:params => {:password => "1234", :other_password => "12345", :other_data => "123456"}}})
444
+
445
+ expect(Bugsnag).to have_sent_notification{ |payload|
446
+ event = get_event_from_payload(payload)
447
+ expect(event["metaData"]).not_to be_nil
448
+ expect(event["metaData"]["request"]).not_to be_nil
449
+ expect(event["metaData"]["request"]["params"]).not_to be_nil
450
+ expect(event["metaData"]["request"]["params"]["password"]).to eq("[FILTERED]")
451
+ expect(event["metaData"]["request"]["params"]["other_password"]).to eq("[FILTERED]")
452
+ expect(event["metaData"]["request"]["params"]["other_data"]).to eq("123456")
453
+ }
454
+ end
455
+
456
+ it "filters params from all payload hashes if they are added to params_filters" do
457
+
458
+ Bugsnag.configuration.params_filters << "other_data"
459
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {:request => {:params => {:password => "1234", :other_password => "123456", :other_data => "123456"}}})
460
+
461
+ expect(Bugsnag).to have_sent_notification{ |payload|
462
+ event = get_event_from_payload(payload)
463
+ expect(event["metaData"]).not_to be_nil
464
+ expect(event["metaData"]["request"]).not_to be_nil
465
+ expect(event["metaData"]["request"]["params"]).not_to be_nil
466
+ expect(event["metaData"]["request"]["params"]["password"]).to eq("[FILTERED]")
467
+ expect(event["metaData"]["request"]["params"]["other_password"]).to eq("[FILTERED]")
468
+ expect(event["metaData"]["request"]["params"]["other_data"]).to eq("[FILTERED]")
469
+ }
470
+ end
471
+
472
+ it "filters params from all payload hashes if they are added to params_filters as regex" do
473
+
474
+ Bugsnag.configuration.params_filters << /other_data/
475
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {:request => {:params => {:password => "1234", :other_password => "123456", :other_data => "123456"}}})
476
+
477
+ expect(Bugsnag).to have_sent_notification{ |payload|
478
+ event = get_event_from_payload(payload)
479
+ expect(event["metaData"]).not_to be_nil
480
+ expect(event["metaData"]["request"]).not_to be_nil
481
+ expect(event["metaData"]["request"]["params"]).not_to be_nil
482
+ expect(event["metaData"]["request"]["params"]["password"]).to eq("[FILTERED]")
483
+ expect(event["metaData"]["request"]["params"]["other_password"]).to eq("[FILTERED]")
484
+ expect(event["metaData"]["request"]["params"]["other_data"]).to eq("[FILTERED]")
485
+ }
486
+ end
487
+
488
+ it "filters params from all payload hashes if they are added to params_filters as partial regex" do
489
+
490
+ Bugsnag.configuration.params_filters << /r_data/
491
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {:request => {:params => {:password => "1234", :other_password => "123456", :other_data => "123456"}}})
492
+
493
+ expect(Bugsnag).to have_sent_notification{ |payload|
494
+ event = get_event_from_payload(payload)
495
+ expect(event["metaData"]).not_to be_nil
496
+ expect(event["metaData"]["request"]).not_to be_nil
497
+ expect(event["metaData"]["request"]["params"]).not_to be_nil
498
+ expect(event["metaData"]["request"]["params"]["password"]).to eq("[FILTERED]")
499
+ expect(event["metaData"]["request"]["params"]["other_password"]).to eq("[FILTERED]")
500
+ expect(event["metaData"]["request"]["params"]["other_data"]).to eq("[FILTERED]")
501
+ }
502
+ end
503
+
504
+ it "does not filter params from payload hashes if their values are nil" do
505
+ Bugsnag.notify(BugsnagTestException.new("It crashed"), {:request => {:params => {:nil_param => nil}}})
506
+
507
+ expect(Bugsnag).to have_sent_notification{ |payload|
508
+ event = get_event_from_payload(payload)
509
+ expect(event["metaData"]).not_to be_nil
510
+ expect(event["metaData"]["request"]).not_to be_nil
511
+ expect(event["metaData"]["request"]["params"]).not_to be_nil
512
+ expect(event["metaData"]["request"]["params"]).to have_key("nil_param")
513
+ }
514
+ end
515
+
516
+ it "does not notify if the exception class is in the default ignore_classes list" do
517
+ Bugsnag.notify_or_ignore(ActiveRecord::RecordNotFound.new("It crashed"))
518
+
519
+ expect(Bugsnag).not_to have_sent_notification
520
+ end
521
+
522
+ it "does not notify if the non-default exception class is added to the ignore_classes" do
523
+ Bugsnag.configuration.ignore_classes << "BugsnagTestException"
524
+
525
+ Bugsnag.notify_or_ignore(BugsnagTestException.new("It crashed"))
526
+
527
+ expect(Bugsnag).not_to have_sent_notification
528
+ end
529
+
530
+ it "does not notify if exception's ancestor is an ignored class" do
531
+ Bugsnag.configuration.ignore_classes << "BugsnagTestException"
532
+
533
+ Bugsnag.notify_or_ignore(BugsnagSubclassTestException.new("It crashed"))
534
+
535
+ expect(Bugsnag).not_to have_sent_notification
536
+ end
537
+
538
+ it "does not notify if any caused exception is an ignored class" do
539
+ Bugsnag.configuration.ignore_classes << "NestedException"
540
+
541
+ ex = NestedException.new("Self-referential exception")
542
+ ex.original_exception = BugsnagTestException.new("It crashed")
543
+
544
+ Bugsnag.notify_or_ignore(ex)
545
+
546
+ expect(Bugsnag).not_to have_sent_notification
547
+ end
548
+
549
+ it "accepts both String and Class instances as an ignored class" do
550
+ Bugsnag.configuration.ignore_classes << BugsnagTestException
551
+
552
+ Bugsnag.notify_or_ignore(BugsnagTestException.new("It crashed"))
553
+
554
+ expect(Bugsnag).not_to have_sent_notification
555
+ end
556
+
557
+ it "does not notify if the user agent is present and matches a regex in ignore_user_agents" do
558
+ Bugsnag.configuration.ignore_user_agents << %r{BugsnagUserAgent}
559
+
560
+ ((Thread.current["bugsnag_req_data"] ||= {})[:rack_env] ||= {})["HTTP_USER_AGENT"] = "BugsnagUserAgent"
561
+
562
+ Bugsnag.notify_or_ignore(BugsnagTestException.new("It crashed"))
563
+
564
+ expect(Bugsnag::Notification).not_to have_sent_notification
565
+ end
566
+
567
+ it "sends the cause of the exception" do
568
+ begin
569
+ begin
570
+ raise "jiminey"
571
+ rescue
572
+ Ruby21Exception.raise! "cricket"
573
+ end
574
+ rescue
575
+ Bugsnag.notify $!
576
+ end
577
+
578
+ expect(Bugsnag).to have_sent_notification{ |payload|
579
+ event = get_event_from_payload(payload)
580
+ expect(event["exceptions"].size).to eq(2)
581
+ }
582
+ end
583
+
584
+ it "does not unwrap the same exception twice" do
585
+ ex = NestedException.new("Self-referential exception")
586
+ ex.original_exception = ex
587
+
588
+ Bugsnag.notify_or_ignore(ex)
589
+
590
+ expect(Bugsnag).to have_sent_notification{ |payload|
591
+ event = get_event_from_payload(payload)
592
+ expect(event["exceptions"].size).to eq(1)
593
+ }
594
+ end
595
+
596
+ it "does not unwrap more than 5 exceptions" do
597
+
598
+ first_ex = ex = NestedException.new("Deep exception")
599
+ 10.times do |idx|
600
+ ex = ex.original_exception = NestedException.new("Deep exception #{idx}")
601
+ end
602
+
603
+ Bugsnag.notify_or_ignore(first_ex)
604
+ expect(Bugsnag).to have_sent_notification{ |payload|
605
+ event = get_event_from_payload(payload)
606
+ expect(event["exceptions"].size).to eq(5)
607
+ }
608
+ end
609
+
610
+ it "calls to_exception on i18n error objects" do
611
+ Bugsnag.notify(OpenStruct.new(:to_exception => BugsnagTestException.new("message")))
612
+
613
+ expect(Bugsnag).to have_sent_notification{ |payload|
614
+ exception = get_exception_from_payload(payload)
615
+ expect(exception["errorClass"]).to eq("BugsnagTestException")
616
+ expect(exception["message"]).to eq("message")
617
+ }
618
+ end
619
+
620
+ it "generates runtimeerror for non exceptions" do
621
+ notify_test_exception
622
+
623
+ expect(Bugsnag).to have_sent_notification{ |payload|
624
+ exception = get_exception_from_payload(payload)
625
+ expect(exception["errorClass"]).to eq("RuntimeError")
626
+ expect(exception["message"]).to eq("test message")
627
+ }
628
+ end
629
+
630
+ it "supports unix-style paths in backtraces" do
631
+ ex = BugsnagTestException.new("It crashed")
632
+ ex.set_backtrace([
633
+ "/Users/james/app/spec/notification_spec.rb:419",
634
+ "/Some/path/rspec/example.rb:113:in `instance_eval'"
635
+ ])
636
+
637
+ Bugsnag.notify(ex)
638
+
639
+ expect(Bugsnag).to have_sent_notification{ |payload|
640
+ exception = get_exception_from_payload(payload)
641
+ expect(exception["stacktrace"].length).to eq(2)
642
+
643
+ line = exception["stacktrace"][0]
644
+ expect(line["file"]).to eq("/Users/james/app/spec/notification_spec.rb")
645
+ expect(line["lineNumber"]).to eq(419)
646
+ expect(line["method"]).to be nil
647
+
648
+ line = exception["stacktrace"][1]
649
+ expect(line["file"]).to eq("/Some/path/rspec/example.rb")
650
+ expect(line["lineNumber"]).to eq(113)
651
+ expect(line["method"]).to eq("instance_eval")
652
+ }
653
+ end
654
+
655
+ it "supports windows-style paths in backtraces" do
656
+ ex = BugsnagTestException.new("It crashed")
657
+ ex.set_backtrace([
658
+ "C:/projects/test/app/controllers/users_controller.rb:13:in `index'",
659
+ "C:/ruby/1.9.1/gems/actionpack-2.3.10/filters.rb:638:in `block in run_before_filters'"
660
+ ])
661
+
662
+ Bugsnag.notify(ex)
663
+
664
+ expect(Bugsnag).to have_sent_notification{ |payload|
665
+ exception = get_exception_from_payload(payload)
666
+ expect(exception["stacktrace"].length).to eq(2)
667
+
668
+ line = exception["stacktrace"][0]
669
+ expect(line["file"]).to eq("C:/projects/test/app/controllers/users_controller.rb")
670
+ expect(line["lineNumber"]).to eq(13)
671
+ expect(line["method"]).to eq("index")
672
+
673
+ line = exception["stacktrace"][1]
674
+ expect(line["file"]).to eq("C:/ruby/1.9.1/gems/actionpack-2.3.10/filters.rb")
675
+ expect(line["lineNumber"]).to eq(638)
676
+ expect(line["method"]).to eq("block in run_before_filters")
677
+ }
678
+ end
679
+
680
+ it "should fix invalid utf8" do
681
+ invalid_data = "fl\xc3ff"
682
+ invalid_data.force_encoding('BINARY') if invalid_data.respond_to?(:force_encoding)
683
+
684
+ notify_test_exception(:fluff => {:fluff => invalid_data})
685
+
686
+ expect(Bugsnag).to have_sent_notification{ |payload|
687
+ if defined?(Encoding::UTF_8)
688
+ expect(payload.to_json).to match(/fl�ff/)
689
+ else
690
+ expect(payload.to_json).to match(/flff/)
691
+ end
692
+ }
693
+ end
694
+
695
+ it "should handle utf8 encoding errors in exceptions_list" do
696
+ invalid_data = "\"foo\xEBbar\""
697
+ invalid_data = invalid_data.force_encoding("utf-8") if invalid_data.respond_to?(:force_encoding)
698
+
699
+ begin
700
+ JSON.parse(invalid_data)
701
+ rescue
702
+ Bugsnag.notify $!
703
+ end
704
+
705
+ expect(Bugsnag).to have_sent_notification { |payload|
706
+ if defined?(Encoding::UTF_8)
707
+ expect(payload.to_json).to match(/foo�bar/)
708
+ else
709
+ expect(payload.to_json).to match(/foobar/)
710
+ end
711
+ }
712
+ end
713
+
714
+ it "should handle utf8 encoding errors in notification context" do
715
+ invalid_data = "\"foo\xEBbar\""
716
+ invalid_data = invalid_data.force_encoding("utf-8") if invalid_data.respond_to?(:force_encoding)
717
+
718
+ begin
719
+ raise
720
+ rescue
721
+ Bugsnag.notify($!, { :context => invalid_data })
722
+ end
723
+
724
+ expect(Bugsnag).to have_sent_notification { |payload|
725
+ if defined?(Encoding::UTF_8)
726
+ expect(payload.to_json).to match(/foo�bar/)
727
+ else
728
+ expect(payload.to_json).to match(/foobar/)
729
+ end
730
+ }
731
+ end
732
+
733
+ it "should handle utf8 encoding errors in notification app fields" do
734
+ invalid_data = "\"foo\xEBbar\""
735
+ invalid_data = invalid_data.force_encoding("utf-8") if invalid_data.respond_to?(:force_encoding)
736
+
737
+ Bugsnag.configuration.app_version = invalid_data
738
+ Bugsnag.configuration.release_stage = invalid_data
739
+ Bugsnag.configuration.app_type = invalid_data
740
+
741
+ begin
742
+ raise
743
+ rescue
744
+ Bugsnag.notify $!
745
+ end
746
+
747
+ expect(Bugsnag).to have_sent_notification { |payload|
748
+ if defined?(Encoding::UTF_8)
749
+ expect(payload.to_json).to match(/foo�bar/)
750
+ else
751
+ expect(payload.to_json).to match(/foobar/)
752
+ end
753
+ }
754
+ end
755
+
756
+ it "should handle utf8 encoding errors in grouping_hash" do
757
+ invalid_data = "\"foo\xEBbar\""
758
+ invalid_data = invalid_data.force_encoding("utf-8") if invalid_data.respond_to?(:force_encoding)
759
+
760
+ Bugsnag.before_notify_callbacks << lambda do |notif|
761
+ notif.grouping_hash = invalid_data
762
+ end
763
+
764
+ begin
765
+ raise
766
+ rescue
767
+ Bugsnag.notify $!
768
+ end
769
+
770
+ expect(Bugsnag).to have_sent_notification { |payload|
771
+ if defined?(Encoding::UTF_8)
772
+ expect(payload.to_json).to match(/foo�bar/)
773
+ else
774
+ expect(payload.to_json).to match(/foobar/)
775
+ end
776
+ }
777
+ end
778
+
779
+ it "should handle utf8 encoding errors in notification user fields" do
780
+ invalid_data = "\"foo\xEBbar\""
781
+ invalid_data = invalid_data.force_encoding("utf-8") if invalid_data.respond_to?(:force_encoding)
782
+
783
+ Bugsnag.before_notify_callbacks << lambda do |notif|
784
+ notif.user = {
785
+ :email => "#{invalid_data}@foo.com",
786
+ :name => invalid_data
787
+ }
788
+ end
789
+
790
+ begin
791
+ raise
792
+ rescue
793
+ Bugsnag.notify $!
794
+ end
795
+
796
+ expect(Bugsnag).to have_sent_notification { |payload|
797
+ if defined?(Encoding::UTF_8)
798
+ expect(payload.to_json).to match(/foo�bar/)
799
+ else
800
+ expect(payload.to_json).to match(/foobar/)
801
+ end
802
+ }
803
+ end
804
+
805
+ if defined?(JRUBY_VERSION)
806
+
807
+ it "should work with java.lang.Throwables" do
808
+ begin
809
+ JRubyException.raise!
810
+ rescue
811
+ Bugsnag.notify $!
812
+ end
813
+
814
+ expect(Bugsnag).to have_sent_notification{ |payload|
815
+ exception = get_exception_from_payload(payload)
816
+ expect(exception["errorClass"]).to eq('Java::JavaLang::ArrayIndexOutOfBoundsException')
817
+ expect(exception["message"]).to eq("2")
818
+ expect(exception["stacktrace"].size).to be > 0
819
+ }
820
+ end
821
+ end
822
+ end