bugsnag 6.10.0 → 6.11.0

Sign up to get free protection for your applications and to get access to all the features.
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