bugsnag 6.10.0 → 6.11.0

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +4 -0
  3. data/CHANGELOG.md +20 -0
  4. data/Gemfile +1 -0
  5. data/README.md +1 -0
  6. data/VERSION +1 -1
  7. data/features/fixtures/docker-compose.yml +13 -0
  8. data/features/fixtures/rails3/app/app/controllers/breadcrumbs_controller.rb +19 -0
  9. data/features/fixtures/rails3/app/app/controllers/session_tracking_controller.rb +10 -6
  10. data/features/fixtures/rails3/app/config/initializers/bugsnag.rb +8 -2
  11. data/features/fixtures/rails3/app/config/routes.rb +1 -0
  12. data/features/fixtures/rails4/app/Gemfile +5 -1
  13. data/features/fixtures/rails4/app/app/controllers/breadcrumbs_controller.rb +26 -0
  14. data/features/fixtures/rails4/app/app/controllers/mongo_controller.rb +23 -0
  15. data/features/fixtures/rails4/app/app/controllers/session_tracking_controller.rb +9 -5
  16. data/features/fixtures/rails4/app/app/jobs/application_job.rb +2 -0
  17. data/features/fixtures/rails4/app/app/jobs/notify_job.rb +5 -0
  18. data/features/fixtures/rails4/app/app/models/mongo_model.rb +6 -0
  19. data/features/fixtures/rails4/app/config/initializers/bugsnag.rb +7 -1
  20. data/features/fixtures/rails4/app/config/mongoid.yml +22 -0
  21. data/features/fixtures/rails4/app/config/routes.rb +2 -0
  22. data/features/fixtures/rails5/app/Gemfile +4 -0
  23. data/features/fixtures/rails5/app/app/controllers/breadcrumbs_controller.rb +24 -0
  24. data/features/fixtures/rails5/app/app/controllers/mongo_controller.rb +22 -0
  25. data/features/fixtures/rails5/app/app/controllers/session_tracking_controller.rb +9 -5
  26. data/features/fixtures/rails5/app/app/jobs/notify_job.rb +5 -0
  27. data/features/fixtures/rails5/app/app/models/mongo_model.rb +6 -0
  28. data/features/fixtures/rails5/app/config/initializers/bugsnag.rb +7 -1
  29. data/features/fixtures/rails5/app/config/mongoid.yml +23 -0
  30. data/features/fixtures/rails5/app/config/routes.rb +11 -1
  31. data/features/rails_features/auto_capture_sessions.feature +55 -5
  32. data/features/rails_features/breadcrumbs.feature +135 -0
  33. data/features/rails_features/mongo_breadcrumbs.feature +100 -0
  34. data/features/steps/ruby_notifier_steps.rb +6 -0
  35. data/lib/bugsnag.rb +59 -3
  36. data/lib/bugsnag/breadcrumbs/breadcrumb.rb +76 -0
  37. data/lib/bugsnag/breadcrumbs/breadcrumbs.rb +14 -0
  38. data/lib/bugsnag/breadcrumbs/validator.rb +59 -0
  39. data/lib/bugsnag/configuration.rb +103 -6
  40. data/lib/bugsnag/integrations/mongo.rb +132 -0
  41. data/lib/bugsnag/integrations/rails/rails_breadcrumbs.rb +118 -0
  42. data/lib/bugsnag/integrations/railtie.rb +28 -1
  43. data/lib/bugsnag/middleware/breadcrumbs.rb +21 -0
  44. data/lib/bugsnag/report.rb +30 -1
  45. data/lib/bugsnag/session_tracker.rb +1 -0
  46. data/lib/bugsnag/utility/circular_buffer.rb +62 -0
  47. data/spec/breadcrumbs/breadcrumb_spec.rb +93 -0
  48. data/spec/breadcrumbs/validator_spec.rb +200 -0
  49. data/spec/bugsnag_spec.rb +230 -0
  50. data/spec/configuration_spec.rb +176 -2
  51. data/spec/integrations/mongo_spec.rb +262 -0
  52. data/spec/report_spec.rb +149 -0
  53. data/spec/session_tracker_spec.rb +24 -2
  54. data/spec/utility/circular_buffer_spec.rb +98 -0
  55. metadata +27 -2
@@ -2,6 +2,10 @@
2
2
  require 'spec_helper'
3
3
 
4
4
  describe Bugsnag do
5
+
6
+ let(:breadcrumbs) { Bugsnag.configuration.breadcrumbs }
7
+ let(:timestamp_regex) { /^\d{4}\-\d{2}\-\d{2}T\d{2}:\d{2}:[\d\.]+Z$/ }
8
+
5
9
  describe 'notify' do
6
10
  before do
7
11
  Bugsnag.configuration.logger = spy('logger')
@@ -11,10 +15,91 @@ describe Bugsnag do
11
15
  notify_test_exception(true)
12
16
  expect(Bugsnag.configuration.logger).to_not have_received(:warn)
13
17
  end
18
+
14
19
  it 'logs an error when sending invalid arguments as auto_notify' do
15
20
  notify_test_exception({severity: 'info'})
16
21
  expect(Bugsnag.configuration.logger).to have_received(:warn)
17
22
  end
23
+
24
+ it 'leaves a breadcrumb after exception delivery' do
25
+ begin
26
+ 1/0
27
+ rescue ZeroDivisionError => e
28
+ Bugsnag.notify(e)
29
+ sent_time = Time.now.utc
30
+ end
31
+ expect(breadcrumbs.to_a.size).to eq(1)
32
+ breadcrumb = breadcrumbs.to_a.first
33
+ expect(breadcrumb.name).to eq('ZeroDivisionError')
34
+ expect(breadcrumb.type).to eq(Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE)
35
+ expect(breadcrumb.auto).to eq(true)
36
+ expect(breadcrumb.meta_data).to eq({
37
+ :error_class => 'ZeroDivisionError',
38
+ :message => 'divided by 0',
39
+ :severity => 'warning'
40
+ })
41
+ expect(breadcrumb.timestamp).to be_within(1).of(sent_time)
42
+ end
43
+
44
+ it 'leave a RuntimeError breadcrumb after string delivery' do
45
+ Bugsnag.notify('notified string')
46
+ sent_time = Time.now.utc
47
+ expect(breadcrumbs.to_a.size).to eq(1)
48
+ breadcrumb = breadcrumbs.to_a.first
49
+ expect(breadcrumb.name).to eq('RuntimeError')
50
+ expect(breadcrumb.type).to eq(Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE)
51
+ expect(breadcrumb.auto).to eq(true)
52
+ expect(breadcrumb.meta_data).to eq({
53
+ :error_class => 'RuntimeError',
54
+ :message => 'notified string',
55
+ :severity => 'warning'
56
+ })
57
+ expect(breadcrumb.timestamp).to be_within(1).of(sent_time)
58
+ end
59
+ end
60
+
61
+ describe '#configure' do
62
+ it 'calls #check_endpoint_setup every time' do
63
+ expect(Bugsnag).to receive(:check_endpoint_setup).twice
64
+
65
+ Bugsnag.configure
66
+ Bugsnag.configure
67
+ end
68
+ end
69
+
70
+ describe '#check_endpoint_setup' do
71
+ let(:custom_notify_endpoint) { "Custom notify endpoint" }
72
+ let(:custom_session_endpoint) { "Custom session endpoint" }
73
+
74
+ it "does nothing for default endpoints or if both endpoints are set" do
75
+ expect(Bugsnag.configuration).not_to receive(:warn)
76
+ Bugsnag.send(:check_endpoint_setup)
77
+
78
+ Bugsnag.configuration.set_endpoints(custom_notify_endpoint, custom_session_endpoint)
79
+ Bugsnag.send(:check_endpoint_setup)
80
+ end
81
+
82
+ it "warns and disables sessions if a notify endpoint is set without a session endpoint" do
83
+ expect(Bugsnag.configuration).to receive(:warn).with("The session endpoint has not been set, all further session capturing will be disabled")
84
+ expect(Bugsnag.configuration).to receive(:disable_sessions)
85
+ Bugsnag.configuration.set_endpoints(custom_notify_endpoint, nil)
86
+ Bugsnag.send(:check_endpoint_setup)
87
+ end
88
+
89
+ it "raises an ArgumentError if a session endpoint is set without a notify endpoint" do
90
+ Bugsnag.configuration.set_endpoints(nil, "custom session endpoint")
91
+ expect{ Bugsnag.send(:check_endpoint_setup) }.to raise_error(ArgumentError, "The session endpoint cannot be modified without the notify endpoint")
92
+ end
93
+
94
+ it "is called after the configuration block has returned" do
95
+ expect(Bugsnag.configuration).to receive(:warn).with("The 'endpoint' configuration option is deprecated. The 'set_endpoints' method should be used instead").once
96
+ expect(Bugsnag.configuration).to receive(:warn).with("The 'session_endpoint' configuration option is deprecated. The 'set_endpoints' method should be used instead").once
97
+ expect(Bugsnag.configuration).not_to receive(:warn).with("The session endpoint has not been set, all further session capturing will be disabled")
98
+ Bugsnag.configure do |configuration|
99
+ configuration.endpoint = custom_notify_endpoint
100
+ configuration.session_endpoint = custom_session_endpoint
101
+ end
102
+ end
18
103
  end
19
104
 
20
105
  describe "add_exit_handler" do
@@ -133,4 +218,149 @@ describe Bugsnag do
133
218
  Kernel.send(:remove_const, :REQUIRED)
134
219
  end
135
220
  end
221
+
222
+ describe ".leave_breadcrumb" do
223
+ it "requires only a name argument" do
224
+ Bugsnag.leave_breadcrumb("TestName")
225
+ expect(breadcrumbs.to_a.size).to eq(1)
226
+ expect(breadcrumbs.first.to_h).to match({
227
+ :name => "TestName",
228
+ :type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
229
+ :metaData => {},
230
+ :timestamp => match(timestamp_regex)
231
+ })
232
+ end
233
+
234
+ it "accepts meta_data" do
235
+ Bugsnag.leave_breadcrumb("TestName", { :a => 1, :b => "2" })
236
+ expect(breadcrumbs.to_a.size).to eq(1)
237
+ expect(breadcrumbs.first.to_h).to match({
238
+ :name => "TestName",
239
+ :type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
240
+ :metaData => { :a => 1, :b => "2" },
241
+ :timestamp => match(timestamp_regex)
242
+ })
243
+ end
244
+
245
+ it "allows different message types" do
246
+ Bugsnag.leave_breadcrumb("TestName", {}, Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE)
247
+ expect(breadcrumbs.to_a.size).to eq(1)
248
+ expect(breadcrumbs.first.to_h).to match({
249
+ :name => "TestName",
250
+ :type => Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE,
251
+ :metaData => {},
252
+ :timestamp => match(timestamp_regex)
253
+ })
254
+ end
255
+
256
+ it "validates before leaving" do
257
+ Bugsnag.leave_breadcrumb(
258
+ "123123123123123123123123123123456456456456456456456456456456",
259
+ {
260
+ :a => 1,
261
+ :b => [1, 2, 3, 4],
262
+ :c => {
263
+ :test => true,
264
+ :test2 => false
265
+ }
266
+ },
267
+ "Not a real type"
268
+ )
269
+ expect(breadcrumbs.to_a.size).to eq(1)
270
+ expect(breadcrumbs.first.to_h).to match({
271
+ :name => "123123123123123123123123123123",
272
+ :type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
273
+ :metaData => {
274
+ :a => 1
275
+ },
276
+ :timestamp => match(timestamp_regex)
277
+ })
278
+ end
279
+
280
+ it "runs callbacks before leaving" do
281
+ Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new do |breadcrumb|
282
+ breadcrumb.meta_data = {
283
+ :callback => true
284
+ }
285
+ end
286
+ Bugsnag.leave_breadcrumb("TestName")
287
+ expect(breadcrumbs.to_a.size).to eq(1)
288
+ expect(breadcrumbs.first.to_h).to match({
289
+ :name => "TestName",
290
+ :type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
291
+ :metaData => {
292
+ :callback => true
293
+ },
294
+ :timestamp => match(timestamp_regex)
295
+ })
296
+ end
297
+
298
+ it "validates after callbacks" do
299
+ Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new do |breadcrumb|
300
+ breadcrumb.meta_data = {
301
+ :int => 1,
302
+ :array => [1, 2, 3],
303
+ :hash => {
304
+ :a => 1,
305
+ :b => 2
306
+ }
307
+ }
308
+ breadcrumb.type = "Not a real type"
309
+ breadcrumb.name = "123123123123123123123123123123456456456456456"
310
+ end
311
+ Bugsnag.leave_breadcrumb("TestName")
312
+ expect(breadcrumbs.to_a.size).to eq(1)
313
+ expect(breadcrumbs.first.to_h).to match({
314
+ :name => "123123123123123123123123123123",
315
+ :type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE,
316
+ :metaData => {
317
+ :int => 1
318
+ },
319
+ :timestamp => match(timestamp_regex)
320
+ })
321
+ end
322
+
323
+ it "doesn't add when ignored by the validator" do
324
+ Bugsnag.configuration.enabled_automatic_breadcrumb_types = []
325
+ Bugsnag.leave_breadcrumb("TestName", {}, Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE, :auto)
326
+ expect(breadcrumbs.to_a.size).to eq(0)
327
+ end
328
+
329
+ it "doesn't add if ignored in a callback" do
330
+ Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new do |breadcrumb|
331
+ breadcrumb.ignore!
332
+ end
333
+ Bugsnag.leave_breadcrumb("TestName")
334
+ expect(breadcrumbs.to_a.size).to eq(0)
335
+ end
336
+
337
+ it "doesn't add when ignored after the callbacks" do
338
+ Bugsnag.configuration.enabled_automatic_breadcrumb_types = [
339
+ Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE
340
+ ]
341
+ Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new do |breadcrumb|
342
+ breadcrumb.type = Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE
343
+ end
344
+ Bugsnag.leave_breadcrumb("TestName", {}, Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE, :auto)
345
+ expect(breadcrumbs.to_a.size).to eq(0)
346
+ end
347
+
348
+ it "doesn't call callbacks if ignored early" do
349
+ Bugsnag.configuration.enabled_automatic_breadcrumb_types = []
350
+ Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new do |breadcrumb|
351
+ fail "This shouldn't be called"
352
+ end
353
+ Bugsnag.leave_breadcrumb("TestName", {}, Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE, :auto)
354
+ end
355
+
356
+ it "doesn't continue to call callbacks if ignored in them" do
357
+ Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new do |breadcrumb|
358
+ breadcrumb.ignore!
359
+ end
360
+ Bugsnag.configuration.before_breadcrumb_callbacks << Proc.new do |breadcrumb|
361
+ fail "This shouldn't be called"
362
+ end
363
+ Bugsnag.leave_breadcrumb("TestName")
364
+ end
365
+ end
136
366
  end
@@ -22,10 +22,93 @@ describe Bugsnag::Configuration do
22
22
  subject.delivery_method = :wow
23
23
  expect(subject.delivery_method).to eq(:wow)
24
24
  end
25
+ end
26
+
27
+ describe "#notify_endpoint" do
28
+ it "defaults to DEFAULT_NOTIFY_ENDPOINT" do
29
+ expect(subject.notify_endpoint).to eq(Bugsnag::Configuration::DEFAULT_NOTIFY_ENDPOINT)
30
+ end
31
+
32
+ it "is readonly" do
33
+ expect{ subject.notify_endpoint = "My Custom Url" }.to raise_error(NoMethodError)
34
+ end
35
+
36
+ it "is the same as endpoint" do
37
+ expect(subject.notify_endpoint).to equal(subject.endpoint)
38
+ end
39
+ end
40
+
41
+ describe "#session_endpoint" do
42
+ it "defaults to DEFAULT_SESSION_ENDPOINT" do
43
+ expect(subject.session_endpoint).to eq(Bugsnag::Configuration::DEFAULT_SESSION_ENDPOINT)
44
+ end
45
+ end
46
+
47
+ describe "#auto_capture_sessions" do
48
+ it "defaults to true" do
49
+ expect(subject.auto_capture_sessions).to eq(true)
50
+ end
51
+ end
52
+
53
+ describe "#enable_sessions" do
54
+ it "defaults to true" do
55
+ expect(subject.enable_sessions).to eq(true)
56
+ end
57
+
58
+ it "is readonly" do
59
+ expect{ subject.enable_sessions = true }.to raise_error(NoMethodError)
60
+ end
61
+ end
62
+
63
+ describe "#endpoint=" do
64
+ let(:custom_notify_endpoint) { "My custom notify endpoint" }
65
+ let(:session_endpoint) { "My session endpoint" }
66
+ it "calls #warn with a deprecation notice" do
67
+ allow(subject).to receive(:set_endpoints)
68
+ expect(subject).to receive(:warn).with("The 'endpoint' configuration option is deprecated. The 'set_endpoints' method should be used instead")
69
+ subject.endpoint = custom_notify_endpoint
70
+ end
71
+
72
+ it "calls #set_endpoints with the new notify_endpoint and existing session endpoint" do
73
+ allow(subject).to receive(:session_endpoint).and_return(session_endpoint)
74
+ allow(subject).to receive(:warn)
75
+ expect(subject).to receive(:set_endpoints).with(custom_notify_endpoint, session_endpoint)
76
+ subject.endpoint = custom_notify_endpoint
77
+ end
78
+ end
79
+
80
+ describe "#session_endpoint=" do
81
+ let(:notify_endpoint) { "My notify endpoint" }
82
+ let(:custom_session_endpoint) { "My custom session endpoint" }
83
+ it "calls #warn with a deprecation notice" do
84
+ allow(subject).to receive(:set_endpoints)
85
+ expect(subject).to receive(:warn).with("The 'session_endpoint' configuration option is deprecated. The 'set_endpoints' method should be used instead")
86
+ subject.session_endpoint = custom_session_endpoint
87
+ end
88
+
89
+ it "calls #set_endpoints with the existing notify_endpoint and new session endpoint" do
90
+ allow(subject).to receive(:notify_endpoint).and_return(notify_endpoint)
91
+ allow(subject).to receive(:warn)
92
+ expect(subject).to receive(:set_endpoints).with(notify_endpoint, custom_session_endpoint)
93
+ subject.session_endpoint = custom_session_endpoint
94
+ end
95
+ end
96
+
97
+ describe "#set_endpoints" do
98
+ let(:custom_notify_endpoint) { "My custom notify endpoint" }
99
+ let(:custom_session_endpoint) { "My custom session endpoint" }
100
+ it "set notify_endpoint and session_endpoint" do
101
+ subject.set_endpoints(custom_notify_endpoint, custom_session_endpoint)
102
+ expect(subject.notify_endpoint).to eq(custom_notify_endpoint)
103
+ expect(subject.session_endpoint).to eq(custom_session_endpoint)
104
+ end
105
+ end
25
106
 
26
- it "should have sensible defaults for session tracking" do
27
- expect(subject.session_endpoint).to eq("https://sessions.bugsnag.com")
107
+ describe "#disable_sessions" do
108
+ it "sets #send_session and #auto_capture_sessions to false" do
109
+ subject.disable_sessions
28
110
  expect(subject.auto_capture_sessions).to be false
111
+ expect(subject.enable_sessions).to be false
29
112
  end
30
113
  end
31
114
 
@@ -222,4 +305,95 @@ describe Bugsnag::Configuration do
222
305
  expect(subject.ignore_classes).to eq(Set.new([SystemExit, SignalException]))
223
306
  end
224
307
 
308
+ describe "#breadcrumbs" do
309
+ it "first returns a new circular buffer" do
310
+ buffer = subject.breadcrumbs
311
+
312
+ expect(buffer).to be_a(Bugsnag::Utility::CircularBuffer)
313
+ expect(buffer.to_a).to eq([])
314
+ end
315
+
316
+ it "returns the same buffer in repeated calls" do
317
+ buffer = subject.breadcrumbs
318
+ buffer << 1
319
+ second_buffer = subject.breadcrumbs
320
+
321
+ expect(second_buffer.to_a).to eq([1])
322
+ end
323
+
324
+ it "returns a different buffer on different threads" do
325
+ buffer = subject.breadcrumbs
326
+ buffer << 1
327
+
328
+ second_buffer = nil
329
+ Thread.new { second_buffer = subject.breadcrumbs; second_buffer << 2 }.join
330
+
331
+ expect(buffer.to_a).to eq([1])
332
+ expect(second_buffer.to_a).to eq([2])
333
+ end
334
+
335
+ it "sets max_items to the current max_breadcrumbs size" do
336
+ expect(subject.breadcrumbs.max_items).to eq(subject.max_breadcrumbs)
337
+ end
338
+ end
339
+
340
+ describe "#max_breadcrumbs" do
341
+ it "defaults to DEFAULT_MAX_BREADCRUMBS" do
342
+ expect(subject.max_breadcrumbs).to eq(Bugsnag::Configuration::DEFAULT_MAX_BREADCRUMBS)
343
+ end
344
+ end
345
+
346
+ describe "#max_breadcrumbs=" do
347
+ it "sets the value of max_breadcrumbs" do
348
+ subject.max_breadcrumbs = 10
349
+ expect(subject.max_breadcrumbs).to eq(10)
350
+ end
351
+
352
+ it "sets the max_items property of the breadcrumbs buffer" do
353
+ buffer = subject.breadcrumbs
354
+
355
+ expect(buffer.max_items).to eq(Bugsnag::Configuration::DEFAULT_MAX_BREADCRUMBS)
356
+
357
+ subject.max_breadcrumbs = 5
358
+
359
+ expect(buffer.max_items).to eq(5)
360
+ end
361
+ end
362
+
363
+ describe "#enabled_automatic_breadcrumb_types" do
364
+ it "defaults to Bugsnag::Breadcrumbs::VALID_BREADCRUMB_TYPES" do
365
+ expect(subject.enabled_automatic_breadcrumb_types).to eq(Bugsnag::Breadcrumbs::VALID_BREADCRUMB_TYPES)
366
+ end
367
+
368
+ it "is an editable array" do
369
+ subject.enabled_automatic_breadcrumb_types << "Some custom type"
370
+ expect(subject.enabled_automatic_breadcrumb_types).to include("Some custom type")
371
+ end
372
+ end
373
+
374
+ describe "#before_breadcrumb_callbacks" do
375
+ it "initially returns an empty array" do
376
+ expect(subject.before_breadcrumb_callbacks).to eq([])
377
+ end
378
+
379
+ it "stores the array between subsequent calls" do
380
+ first_call = subject.before_breadcrumb_callbacks
381
+ first_call << 1
382
+
383
+ second_call = subject.before_breadcrumb_callbacks
384
+
385
+ expect(second_call).to eq([1])
386
+ end
387
+
388
+ it "stays the same across threads" do
389
+ first_array = subject.before_breadcrumb_callbacks
390
+ first_array << 1
391
+
392
+ second_array = nil
393
+ Thread.new { second_array = subject.before_breadcrumb_callbacks; second_array << 2}.join
394
+
395
+ expect(first_array).to eq([1, 2])
396
+ expect(second_array).to eq([1, 2])
397
+ end
398
+ end
225
399
  end
@@ -0,0 +1,262 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe 'Bugsnag::MongoBreadcrumbSubscriber', :order => :defined do
5
+ before do
6
+ unless defined?(::Mongo)
7
+ @mocked_mongo = true
8
+ module ::Mongo
9
+ module Monitoring
10
+ COMMAND = 'Command'
11
+ module Global
12
+ end
13
+ end
14
+ end
15
+ module Kernel
16
+ alias_method :old_require, :require
17
+ def require(path)
18
+ old_require(path) unless path == 'mongo'
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ it "should subscribe to the mongo monitoring service" do
25
+ expect(::Mongo::Monitoring::Global).to receive(:subscribe) do |command, subscriber|
26
+ expect(command).to eq(::Mongo::Monitoring::COMMAND)
27
+ expect(subscriber).to be_an_instance_of(::Bugsnag::MongoBreadcrumbSubscriber)
28
+ end
29
+ load './lib/bugsnag/integrations/mongo.rb'
30
+ end
31
+
32
+ context "with the module loaded" do
33
+ before do
34
+ allow(::Mongo::Monitoring::Global).to receive(:subscribe)
35
+ require './lib/bugsnag/integrations/mongo'
36
+ end
37
+
38
+ let(:subscriber) { Bugsnag::MongoBreadcrumbSubscriber.new }
39
+
40
+ describe "#started" do
41
+ it "calls #leave_command with the event" do
42
+ event = double
43
+ expect(subscriber).to receive(:leave_command).with(event)
44
+ subscriber.started(event)
45
+ end
46
+ end
47
+
48
+ describe "#succeeded" do
49
+ it "calls #leave_mongo_breadcrumb with the event_name and event" do
50
+ event = double
51
+ expect(subscriber).to receive(:leave_mongo_breadcrumb).with("succeeded", event)
52
+ subscriber.succeeded(event)
53
+ end
54
+ end
55
+
56
+ describe "#failed" do
57
+ it "calls #leave_mongo_breadcrumb with the event_name and event" do
58
+ event = double
59
+ expect(subscriber).to receive(:leave_mongo_breadcrumb).with("failed", event)
60
+ subscriber.failed(event)
61
+ end
62
+ end
63
+
64
+ describe "#leave_mongo_breadcrumb" do
65
+ let(:event) { double(
66
+ :command_name => "command",
67
+ :database_name => "database",
68
+ :operation_id => "1234567890",
69
+ :request_id => "123456",
70
+ :duration => "123.456"
71
+ ) }
72
+ let(:event_name) { "event_name" }
73
+ it "leaves a breadcrumb with relevant meta_data, message, type, and automatic notation" do
74
+ expect(Bugsnag).to receive(:leave_breadcrumb).with(
75
+ "Mongo query #{event_name}",
76
+ {
77
+ :event_name => "mongo.#{event_name}",
78
+ :command_name => "command",
79
+ :database_name => "database",
80
+ :operation_id => "1234567890",
81
+ :request_id => "123456",
82
+ :duration => "123.456"
83
+ },
84
+ "process",
85
+ :auto
86
+ )
87
+ subscriber.send(:leave_mongo_breadcrumb, event_name, event)
88
+ end
89
+
90
+ it "adds message data if present" do
91
+ allow(event).to receive(:message).and_return("This is a message")
92
+ expect(Bugsnag).to receive(:leave_breadcrumb).with(
93
+ "Mongo query #{event_name}",
94
+ {
95
+ :event_name => "mongo.#{event_name}",
96
+ :command_name => "command",
97
+ :database_name => "database",
98
+ :operation_id => "1234567890",
99
+ :request_id => "123456",
100
+ :duration => "123.456",
101
+ :message => "This is a message"
102
+ },
103
+ "process",
104
+ :auto
105
+ )
106
+ subscriber.send(:leave_mongo_breadcrumb, event_name, event)
107
+ end
108
+
109
+ context "command data is present" do
110
+ let(:command) {
111
+ {
112
+ "command" => "collection_name_command",
113
+ "collection" => "collection_name_getMore",
114
+ "filter" => nil
115
+ }
116
+ }
117
+
118
+ it "adds the collection name" do
119
+ expect(subscriber).to receive(:pop_command).with("123456").and_return(command)
120
+ expect(Bugsnag).to receive(:leave_breadcrumb).with(
121
+ "Mongo query #{event_name}",
122
+ {
123
+ :event_name => "mongo.#{event_name}",
124
+ :command_name => "command",
125
+ :database_name => "database",
126
+ :operation_id => "1234567890",
127
+ :request_id => "123456",
128
+ :duration => "123.456",
129
+ :collection => "collection_name_command"
130
+ },
131
+ "process",
132
+ :auto
133
+ )
134
+ subscriber.send(:leave_mongo_breadcrumb, event_name, event)
135
+ end
136
+
137
+ it "adds the correct collection name for 'getMore' commands" do
138
+ allow(event).to receive(:command_name).and_return("getMore")
139
+ expect(subscriber).to receive(:pop_command).with("123456").and_return(command)
140
+ expect(Bugsnag).to receive(:leave_breadcrumb).with(
141
+ "Mongo query #{event_name}",
142
+ {
143
+ :event_name => "mongo.#{event_name}",
144
+ :command_name => "getMore",
145
+ :database_name => "database",
146
+ :operation_id => "1234567890",
147
+ :request_id => "123456",
148
+ :duration => "123.456",
149
+ :collection => "collection_name_getMore"
150
+ },
151
+ "process",
152
+ :auto
153
+ )
154
+ subscriber.send(:leave_mongo_breadcrumb, event_name, event)
155
+ end
156
+
157
+ it "adds a JSON string of filter data" do
158
+ command["filter"] = {"a" => 1, "b" => 2, "$or" => [{"c" => 3}, {"d" => 4}]}
159
+ expect(subscriber).to receive(:pop_command).with("123456").and_return(command)
160
+ expect(Bugsnag).to receive(:leave_breadcrumb).with(
161
+ "Mongo query #{event_name}",
162
+ {
163
+ :event_name => "mongo.#{event_name}",
164
+ :command_name => "command",
165
+ :database_name => "database",
166
+ :operation_id => "1234567890",
167
+ :request_id => "123456",
168
+ :duration => "123.456",
169
+ :collection => "collection_name_command",
170
+ :filter => '{"a":"?","b":"?","$or":[{"c":"?"},{"d":"?"}]}'
171
+ },
172
+ "process",
173
+ :auto
174
+ )
175
+ subscriber.send(:leave_mongo_breadcrumb, event_name, event)
176
+ end
177
+ end
178
+ end
179
+
180
+ describe "#sanitize_filter_hash" do
181
+ it "calls into #sanitize_filter_value with the value from each {k,v} pair" do
182
+ expect(subscriber.send(:sanitize_filter_hash, {:a => 1, :b => 2})).to eq({:a => '?', :b => '?'})
183
+ end
184
+ end
185
+
186
+ describe "#sanitize_filter_value" do
187
+ it "returns '?' for strings, numbers, booleans, and nil" do
188
+ expect(subscriber.send(:sanitize_filter_value, 523, 0)).to eq('?')
189
+ expect(subscriber.send(:sanitize_filter_value, "string", 0)).to eq('?')
190
+ expect(subscriber.send(:sanitize_filter_value, true, 0)).to eq('?')
191
+ expect(subscriber.send(:sanitize_filter_value, nil, 0)).to eq('?')
192
+ end
193
+
194
+ it "is recursive and iterative for array values" do
195
+ expect(subscriber.send(:sanitize_filter_value, [1, [2, [3]]], 0)).to eq(['?', ['?', ['?']]])
196
+ end
197
+
198
+ it "calls #sanitize_filter_hash for hash values" do
199
+ expect(subscriber).to receive(:sanitize_filter_hash).with({:a => 1}, 1)
200
+ subscriber.send(:sanitize_filter_value, {:a => 1}, 0)
201
+ end
202
+
203
+ it "returns [MAX_FILTER_DEPTH_REACHED] if the filter depth is exceeded" do
204
+ expect(subscriber.send(:sanitize_filter_value, 1, 4)).to eq('[MAX_FILTER_DEPTH_REACHED]')
205
+ end
206
+ end
207
+
208
+ describe "#leave_command" do
209
+ it "extracts and stores the command by request_id" do
210
+ request_id = "123456"
211
+ command = "this is a command string"
212
+ event = double(:command => command, :request_id => request_id)
213
+
214
+ subscriber.send(:leave_command, event)
215
+ command_hash = Bugsnag.configuration.request_data[Bugsnag::MongoBreadcrumbSubscriber::MONGO_COMMAND_KEY]
216
+ expect(command_hash[request_id]).to eq(command)
217
+ end
218
+ end
219
+
220
+ describe "#pop_command" do
221
+ let(:request_id) { "123456" }
222
+ let(:command) { "this is a command string" }
223
+ before do
224
+ event = double(:command => command, :request_id => request_id)
225
+ subscriber.send(:leave_command, event)
226
+ end
227
+
228
+ it "returns the command given a request_id" do
229
+ expect(subscriber.send(:pop_command, request_id)).to eq(command)
230
+ end
231
+
232
+ it "removes the command from the request_data" do
233
+ subscriber.send(:pop_command, request_id)
234
+ command_hash = Bugsnag.configuration.request_data[Bugsnag::MongoBreadcrumbSubscriber::MONGO_COMMAND_KEY]
235
+ expect(command_hash).not_to have_key(request_id)
236
+ end
237
+
238
+ it "returns nil if the request_id is not found" do
239
+ expect(subscriber.send(:pop_command, "09876")).to be_nil
240
+ end
241
+ end
242
+
243
+ describe "#event_commands" do
244
+ it "returns a hash" do
245
+ expect(subscriber.send(:event_commands)).to be_a(Hash)
246
+ end
247
+
248
+ it "is stored in request data" do
249
+ subscriber.send(:event_commands)[:key] = "value"
250
+ command_hash = Bugsnag.configuration.request_data[Bugsnag::MongoBreadcrumbSubscriber::MONGO_COMMAND_KEY]
251
+ expect(command_hash[:key]).to eq("value")
252
+ end
253
+ end
254
+ end
255
+
256
+ after do
257
+ Object.send(:remove_const, :Mongo) if @mocked_mongo
258
+ module Kernel
259
+ alias_method :require, :old_require
260
+ end
261
+ end
262
+ end