airbrake-ruby 4.15.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby/async_sender.rb +4 -2
  3. data/lib/airbrake-ruby/backtrace.rb +6 -5
  4. data/lib/airbrake-ruby/config/processor.rb +77 -0
  5. data/lib/airbrake-ruby/config/validator.rb +6 -0
  6. data/lib/airbrake-ruby/config.rb +44 -35
  7. data/lib/airbrake-ruby/context.rb +51 -0
  8. data/lib/airbrake-ruby/file_cache.rb +1 -1
  9. data/lib/airbrake-ruby/filter_chain.rb +3 -0
  10. data/lib/airbrake-ruby/filters/context_filter.rb +4 -5
  11. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  12. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +1 -1
  13. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  14. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +3 -4
  15. data/lib/airbrake-ruby/filters/git_repository_filter.rb +11 -2
  16. data/lib/airbrake-ruby/filters/git_revision_filter.rb +3 -1
  17. data/lib/airbrake-ruby/filters/keys_filter.rb +23 -15
  18. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  19. data/lib/airbrake-ruby/filters/sql_filter.rb +11 -11
  20. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  21. data/lib/airbrake-ruby/filters/thread_filter.rb +4 -3
  22. data/lib/airbrake-ruby/grouppable.rb +1 -1
  23. data/lib/airbrake-ruby/ignorable.rb +1 -2
  24. data/lib/airbrake-ruby/mergeable.rb +1 -1
  25. data/lib/airbrake-ruby/monotonic_time.rb +1 -1
  26. data/lib/airbrake-ruby/notice.rb +1 -8
  27. data/lib/airbrake-ruby/notice_notifier.rb +4 -4
  28. data/lib/airbrake-ruby/performance_breakdown.rb +1 -6
  29. data/lib/airbrake-ruby/performance_notifier.rb +40 -54
  30. data/lib/airbrake-ruby/promise.rb +1 -0
  31. data/lib/airbrake-ruby/query.rb +1 -6
  32. data/lib/airbrake-ruby/queue.rb +1 -8
  33. data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
  34. data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
  35. data/lib/airbrake-ruby/remote_settings.rb +128 -0
  36. data/lib/airbrake-ruby/request.rb +1 -8
  37. data/lib/airbrake-ruby/stat.rb +2 -13
  38. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  39. data/lib/airbrake-ruby/tdigest.rb +12 -9
  40. data/lib/airbrake-ruby/thread_pool.rb +9 -6
  41. data/lib/airbrake-ruby/time_truncate.rb +2 -2
  42. data/lib/airbrake-ruby/timed_trace.rb +1 -3
  43. data/lib/airbrake-ruby/truncator.rb +8 -2
  44. data/lib/airbrake-ruby/version.rb +11 -1
  45. data/lib/airbrake-ruby.rb +44 -54
  46. data/spec/airbrake_spec.rb +178 -92
  47. data/spec/async_sender_spec.rb +10 -8
  48. data/spec/backtrace_spec.rb +39 -36
  49. data/spec/benchmark_spec.rb +7 -5
  50. data/spec/code_hunk_spec.rb +26 -17
  51. data/spec/config/processor_spec.rb +167 -0
  52. data/spec/config/validator_spec.rb +23 -3
  53. data/spec/config_spec.rb +43 -55
  54. data/spec/context_spec.rb +54 -0
  55. data/spec/deploy_notifier_spec.rb +6 -4
  56. data/spec/file_cache_spec.rb +1 -0
  57. data/spec/filter_chain_spec.rb +29 -24
  58. data/spec/filters/context_filter_spec.rb +14 -5
  59. data/spec/filters/dependency_filter_spec.rb +3 -1
  60. data/spec/filters/exception_attributes_filter_spec.rb +5 -3
  61. data/spec/filters/gem_root_filter_spec.rb +9 -6
  62. data/spec/filters/git_last_checkout_filter_spec.rb +10 -12
  63. data/spec/filters/{git_repository_filter.rb → git_repository_filter_spec.rb} +26 -15
  64. data/spec/filters/git_revision_filter_spec.rb +20 -20
  65. data/spec/filters/keys_allowlist_spec.rb +26 -16
  66. data/spec/filters/keys_blocklist_spec.rb +35 -18
  67. data/spec/filters/root_directory_filter_spec.rb +7 -7
  68. data/spec/filters/sql_filter_spec.rb +28 -28
  69. data/spec/filters/system_exit_filter_spec.rb +4 -2
  70. data/spec/filters/thread_filter_spec.rb +16 -14
  71. data/spec/loggable_spec.rb +2 -2
  72. data/spec/monotonic_time_spec.rb +8 -6
  73. data/spec/nested_exception_spec.rb +46 -46
  74. data/spec/notice_notifier/options_spec.rb +25 -15
  75. data/spec/notice_notifier_spec.rb +54 -49
  76. data/spec/notice_spec.rb +7 -3
  77. data/spec/performance_breakdown_spec.rb +0 -12
  78. data/spec/performance_notifier_spec.rb +69 -87
  79. data/spec/promise_spec.rb +38 -32
  80. data/spec/query_spec.rb +1 -11
  81. data/spec/queue_spec.rb +1 -13
  82. data/spec/remote_settings/callback_spec.rb +162 -0
  83. data/spec/remote_settings/settings_data_spec.rb +348 -0
  84. data/spec/remote_settings_spec.rb +201 -0
  85. data/spec/request_spec.rb +1 -13
  86. data/spec/response_spec.rb +34 -12
  87. data/spec/spec_helper.rb +4 -4
  88. data/spec/stashable_spec.rb +5 -5
  89. data/spec/stat_spec.rb +7 -14
  90. data/spec/sync_sender_spec.rb +52 -17
  91. data/spec/tdigest_spec.rb +61 -56
  92. data/spec/thread_pool_spec.rb +67 -58
  93. data/spec/time_truncate_spec.rb +23 -6
  94. data/spec/timed_trace_spec.rb +32 -30
  95. data/spec/truncator_spec.rb +72 -43
  96. metadata +67 -51
@@ -1,4 +1,6 @@
1
1
  RSpec.describe Airbrake::NoticeNotifier do
2
+ subject(:notice_notifier) { described_class.new }
3
+
2
4
  before do
3
5
  Airbrake::Config.instance = Airbrake::Config.new(
4
6
  project_id: 1,
@@ -15,13 +17,13 @@ RSpec.describe Airbrake::NoticeNotifier do
15
17
  it "appends the context filter" do
16
18
  expect_any_instance_of(Airbrake::FilterChain).to receive(:add_filter)
17
19
  .with(instance_of(Airbrake::Filters::ContextFilter))
18
- subject
20
+ notice_notifier
19
21
  end
20
22
 
21
23
  it "appends the exception attributes filter" do
22
24
  expect_any_instance_of(Airbrake::FilterChain).to receive(:add_filter)
23
25
  .with(instance_of(Airbrake::Filters::ExceptionAttributesFilter))
24
- subject
26
+ notice_notifier
25
27
  end
26
28
  end
27
29
  end
@@ -39,14 +41,14 @@ RSpec.describe Airbrake::NoticeNotifier do
39
41
  before { stub_request(:post, endpoint).to_return(status: 201, body: body) }
40
42
 
41
43
  it "returns a promise" do
42
- expect(subject.notify('ex')).to be_an(Airbrake::Promise)
44
+ expect(notice_notifier.notify('ex')).to be_an(Airbrake::Promise)
43
45
  sleep 1
44
46
  end
45
47
 
46
48
  it "refines the notice object" do
47
- subject.add_filter { |n| n[:params] = { foo: 'bar' } }
48
- notice = subject.build_notice('ex')
49
- subject.notify(notice)
49
+ notice_notifier.add_filter { |n| n[:params] = { foo: 'bar' } }
50
+ notice = notice_notifier.build_notice('ex')
51
+ notice_notifier.notify(notice)
50
52
  expect(notice[:params]).to eq(foo: 'bar')
51
53
  sleep 1
52
54
  end
@@ -55,54 +57,55 @@ RSpec.describe Airbrake::NoticeNotifier do
55
57
  before { Airbrake::Config.instance.merge(project_id: nil) }
56
58
 
57
59
  it "returns a rejected promise" do
58
- promise = subject.notify({})
60
+ promise = notice_notifier.notify({})
59
61
  expect(promise).to be_rejected
60
62
  end
61
63
  end
62
64
 
63
65
  context "when a notice is not ignored" do
64
66
  it "yields the notice" do
65
- expect { |b| subject.notify('ex', &b) }
67
+ expect { |b| notice_notifier.notify('ex', &b) }
66
68
  .to yield_with_args(Airbrake::Notice)
67
69
  sleep 1
68
70
  end
69
71
  end
70
72
 
71
73
  context "when a notice is ignored via a filter" do
72
- before { subject.add_filter(&:ignore!) }
74
+ before { notice_notifier.add_filter(&:ignore!) }
73
75
 
74
76
  it "yields the notice" do
75
- expect { |b| subject.notify('ex', &b) }
77
+ expect { |b| notice_notifier.notify('ex', &b) }
76
78
  .to yield_with_args(Airbrake::Notice)
77
79
  end
78
80
 
79
81
  it "returns a rejected promise" do
80
- value = subject.notify('ex').value
82
+ value = notice_notifier.notify('ex').value
81
83
  expect(value['error']).to match(/was marked as ignored/)
82
84
  end
83
85
  end
84
86
 
85
87
  context "when a notice is ignored via an inline filter" do
86
- before { subject.add_filter { raise AirbrakeTestError } }
88
+ before { notice_notifier.add_filter { raise AirbrakeTestError } }
87
89
 
88
90
  it "doesn't invoke regular filters" do
89
- expect { subject.notify('ex', &:ignore!) }.not_to raise_error
91
+ expect { notice_notifier.notify('ex', &:ignore!) }.not_to raise_error
90
92
  end
91
93
  end
92
94
 
93
95
  context "when async sender has workers" do
94
96
  it "sends an exception asynchronously" do
95
97
  expect_any_instance_of(Airbrake::AsyncSender).to receive(:send)
96
- subject.notify('foo', bingo: 'bango')
98
+ notice_notifier.notify('foo', bingo: 'bango')
97
99
  end
98
100
  end
99
101
 
100
102
  context "when async sender doesn't have workers" do
101
103
  it "sends an exception synchronously" do
102
- expect_any_instance_of(Airbrake::AsyncSender)
104
+ allow_any_instance_of(Airbrake::AsyncSender)
103
105
  .to receive(:has_workers?).and_return(false)
104
106
  expect_any_instance_of(Airbrake::SyncSender).to receive(:send)
105
- subject.notify('foo', bingo: 'bango')
107
+
108
+ notice_notifier.notify('foo', bingo: 'bango')
106
109
  end
107
110
  end
108
111
 
@@ -116,11 +119,11 @@ RSpec.describe Airbrake::NoticeNotifier do
116
119
 
117
120
  it "doesn't send an notice" do
118
121
  expect_any_instance_of(Airbrake::AsyncSender).not_to receive(:send)
119
- subject.notify('foo', bingo: 'bango')
122
+ notice_notifier.notify('foo', bingo: 'bango')
120
123
  end
121
124
 
122
125
  it "returns a rejected promise" do
123
- promise = subject.notify('foo', bingo: 'bango')
126
+ promise = notice_notifier.notify('foo', bingo: 'bango')
124
127
  expect(promise.value).to eq('error' => "current environment 'test' is ignored")
125
128
  end
126
129
  end
@@ -139,18 +142,18 @@ RSpec.describe Airbrake::NoticeNotifier do
139
142
  before { stub_request(:post, endpoint).to_return(status: 201, body: body.to_json) }
140
143
 
141
144
  it "returns a reponse hash" do
142
- expect(subject.notify_sync('ex')).to eq(body)
145
+ expect(notice_notifier.notify_sync('ex')).to eq(body)
143
146
  end
144
147
 
145
148
  it "refines the notice object" do
146
- subject.add_filter { |n| n[:params] = { foo: 'bar' } }
147
- notice = subject.build_notice('ex')
148
- subject.notify_sync(notice)
149
+ notice_notifier.add_filter { |n| n[:params] = { foo: 'bar' } }
150
+ notice = notice_notifier.build_notice('ex')
151
+ notice_notifier.notify_sync(notice)
149
152
  expect(notice[:params]).to eq(foo: 'bar')
150
153
  end
151
154
 
152
155
  it "sends an exception synchronously" do
153
- subject.notify_sync('foo', bingo: 'bango')
156
+ notice_notifier.notify_sync('foo', bingo: 'bango')
154
157
  expect(
155
158
  a_request(:post, endpoint).with(
156
159
  body: /"params":{.*"bingo":"bango".*}/,
@@ -160,30 +163,30 @@ RSpec.describe Airbrake::NoticeNotifier do
160
163
 
161
164
  context "when a notice is not ignored" do
162
165
  it "yields the notice" do
163
- expect { |b| subject.notify_sync('ex', &b) }
166
+ expect { |b| notice_notifier.notify_sync('ex', &b) }
164
167
  .to yield_with_args(Airbrake::Notice)
165
168
  end
166
169
  end
167
170
 
168
171
  context "when a notice is ignored via a filter" do
169
- before { subject.add_filter(&:ignore!) }
172
+ before { notice_notifier.add_filter(&:ignore!) }
170
173
 
171
174
  it "yields the notice" do
172
- expect { |b| subject.notify_sync('ex', &b) }
175
+ expect { |b| notice_notifier.notify_sync('ex', &b) }
173
176
  .to yield_with_args(Airbrake::Notice)
174
177
  end
175
178
 
176
179
  it "returns an error hash" do
177
- response = subject.notify_sync('ex')
180
+ response = notice_notifier.notify_sync('ex')
178
181
  expect(response['error']).to match(/was marked as ignored/)
179
182
  end
180
183
  end
181
184
 
182
185
  context "when a notice is ignored via an inline filter" do
183
- before { subject.add_filter { raise AirbrakeTestError } }
186
+ before { notice_notifier.add_filter { raise AirbrakeTestError } }
184
187
 
185
188
  it "doesn't invoke regular filters" do
186
- expect { subject.notify('ex', &:ignore!) }.not_to raise_error
189
+ expect { notice_notifier.notify('ex', &:ignore!) }.not_to raise_error
187
190
  end
188
191
  end
189
192
 
@@ -196,11 +199,11 @@ RSpec.describe Airbrake::NoticeNotifier do
196
199
 
197
200
  it "doesn't send an notice" do
198
201
  expect_any_instance_of(Airbrake::SyncSender).not_to receive(:send)
199
- subject.notify_sync('foo', bingo: 'bango')
202
+ notice_notifier.notify_sync('foo', bingo: 'bango')
200
203
  end
201
204
 
202
205
  it "returns an error hash" do
203
- expect(subject.notify_sync('foo'))
206
+ expect(notice_notifier.notify_sync('foo'))
204
207
  .to eq('error' => "current environment 'test' is ignored")
205
208
  end
206
209
  end
@@ -209,17 +212,19 @@ RSpec.describe Airbrake::NoticeNotifier do
209
212
  describe "#add_filter" do
210
213
  context "given a block" do
211
214
  it "appends a new filter to the filter chain" do
212
- notifier = subject
215
+ notifier = notice_notifier
213
216
  b = proc {}
217
+ # rubocop:disable RSpec/StubbedMock
214
218
  expect_any_instance_of(Airbrake::FilterChain)
215
219
  .to receive(:add_filter) { |*args| expect(args.last).to be(b) }
220
+ # rubocop:enable RSpec/StubbedMock
216
221
  notifier.add_filter(&b)
217
222
  end
218
223
  end
219
224
 
220
225
  context "given a class" do
221
226
  it "appends a new filter to the filter chain" do
222
- notifier = subject
227
+ notifier = notice_notifier
223
228
  klass = Class.new
224
229
  expect_any_instance_of(Airbrake::FilterChain)
225
230
  .to receive(:add_filter).with(klass)
@@ -231,14 +236,14 @@ RSpec.describe Airbrake::NoticeNotifier do
231
236
  describe "#build_notice" do
232
237
  context "when given exception is another notice" do
233
238
  it "merges params with the notice" do
234
- notice = subject.build_notice('ex')
235
- other = subject.build_notice(notice, foo: 'bar')
239
+ notice = notice_notifier.build_notice('ex')
240
+ other = notice_notifier.build_notice(notice, foo: 'bar')
236
241
  expect(other[:params]).to eq(foo: 'bar')
237
242
  end
238
243
 
239
- it "it returns the provided notice" do
240
- notice = subject.build_notice('ex')
241
- other = subject.build_notice(notice, foo: 'bar')
244
+ it "returns the provided notice" do
245
+ notice = notice_notifier.build_notice('ex')
246
+ other = notice_notifier.build_notice(notice, foo: 'bar')
242
247
  expect(other).to eq(notice)
243
248
  end
244
249
  end
@@ -246,7 +251,7 @@ RSpec.describe Airbrake::NoticeNotifier do
246
251
  context "when given exception is an Exception" do
247
252
  it "prevents mutation of passed-in params hash" do
248
253
  params = { immutable: true }
249
- notice = subject.build_notice('ex', params)
254
+ notice = notice_notifier.build_notice('ex', params)
250
255
  notice[:params][:mutable] = true
251
256
  expect(params).to eq(immutable: true)
252
257
  end
@@ -260,7 +265,7 @@ RSpec.describe Airbrake::NoticeNotifier do
260
265
  ]
261
266
  allow(Kernel).to receive(:caller).and_return(backtrace)
262
267
 
263
- notice = subject.build_notice(Exception.new)
268
+ notice = notice_notifier.build_notice(Exception.new)
264
269
 
265
270
  expect(notice[:errors].first[:backtrace]).to eq(
266
271
  [
@@ -280,7 +285,7 @@ RSpec.describe Airbrake::NoticeNotifier do
280
285
  ]
281
286
  allow(Kernel).to receive(:caller).and_return(backtrace)
282
287
 
283
- notice = subject.build_notice(Exception.new)
288
+ notice = notice_notifier.build_notice(Exception.new)
284
289
 
285
290
  expect(notice[:errors].first[:backtrace]).to eq(
286
291
  [
@@ -296,7 +301,7 @@ RSpec.describe Airbrake::NoticeNotifier do
296
301
  # TODO: this seems to be bugged. Fix later.
297
302
  context "when given exception is a Java exception", skip: true do
298
303
  before do
299
- expect(Airbrake::Backtrace).to receive(:java_exception?).and_return(true)
304
+ allow(Airbrake::Backtrace).to receive(:java_exception?).and_return(true)
300
305
  end
301
306
 
302
307
  it "automatically generates the backtrace" do
@@ -307,9 +312,9 @@ RSpec.describe Airbrake::NoticeNotifier do
307
312
  ]
308
313
  allow(Kernel).to receive(:caller).and_return(backtrace)
309
314
 
310
- notice = subject.build_notice(Exception.new)
315
+ notice = notice_notifier.build_notice(Exception.new)
311
316
 
312
- # rubocop:disable Metrics/LineLength
317
+ # rubocop:disable Layout/LineLength
313
318
  expect(notice[:errors].first[:backtrace]).to eq(
314
319
  [
315
320
  { file: 'org/jruby/RubyKernel.java', line: 998, function: 'eval' },
@@ -317,18 +322,18 @@ RSpec.describe Airbrake::NoticeNotifier do
317
322
  { file: '/ruby/stdlib/irb.rb:489', line: 489, function: 'block in eval_input' },
318
323
  ],
319
324
  )
320
- # rubocop:enable Metrics/LineLength
325
+ # rubocop:enable Layout/LineLength
321
326
  end
322
327
  end
323
328
 
324
329
  context "when async sender is closed" do
325
330
  before do
326
- expect_any_instance_of(Airbrake::AsyncSender)
331
+ allow_any_instance_of(Airbrake::AsyncSender)
327
332
  .to receive(:closed?).and_return(true)
328
333
  end
329
334
 
330
335
  it "raises error" do
331
- expect { subject.build_notice(Exception.new('oops')) }.to raise_error(
336
+ expect { notice_notifier.build_notice(Exception.new('oops')) }.to raise_error(
332
337
  Airbrake::Error,
333
338
  "Airbrake is closed; can't build exception: Exception: oops",
334
339
  )
@@ -339,7 +344,7 @@ RSpec.describe Airbrake::NoticeNotifier do
339
344
  describe "#close" do
340
345
  it "sends the close message to async sender" do
341
346
  expect_any_instance_of(Airbrake::AsyncSender).to receive(:close)
342
- subject.close
347
+ notice_notifier.close
343
348
  end
344
349
  end
345
350
 
@@ -350,7 +355,7 @@ RSpec.describe Airbrake::NoticeNotifier do
350
355
  describe "#merge_context" do
351
356
  it "merges the provided context with the notice object" do
352
357
  expect_any_instance_of(Hash).to receive(:merge!).with(apples: 'oranges')
353
- subject.merge_context(apples: 'oranges')
358
+ notice_notifier.merge_context(apples: 'oranges')
354
359
  end
355
360
  end
356
361
  end
data/spec/notice_spec.rb CHANGED
@@ -85,7 +85,7 @@ RSpec.describe Airbrake::Notice do
85
85
 
86
86
  context "when truncation failed" do
87
87
  it "returns nil" do
88
- expect_any_instance_of(Airbrake::Truncator)
88
+ allow_any_instance_of(Airbrake::Truncator)
89
89
  .to receive(:reduce_max_size).and_return(0)
90
90
 
91
91
  encoded = Base64.encode64("\xD3\xE6\xBC\x9D\xBA").encode!('ASCII-8BIT')
@@ -103,7 +103,7 @@ RSpec.describe Airbrake::Notice do
103
103
  end
104
104
 
105
105
  describe "object replacement with its string version" do
106
- let(:klass) { Class.new {} }
106
+ let(:klass) { Class.new }
107
107
  let(:ex) { AirbrakeTestError.new }
108
108
  let(:params) { { bingo: [Object.new, klass.new] } }
109
109
  let(:notice) { described_class.new(ex, params) }
@@ -152,6 +152,7 @@ RSpec.describe Airbrake::Notice do
152
152
  # serialize them.
153
153
  #
154
154
  # @see https://goo.gl/0A3xNC
155
+ # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
155
156
  class ObjectWithIoIvars
156
157
  def initialize
157
158
  @bongo = Tempfile.new('bongo').tap(&:close)
@@ -162,8 +163,10 @@ RSpec.describe Airbrake::Notice do
162
163
  raise NotImplementedError
163
164
  end
164
165
  end
166
+ # rubocop:enable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
165
167
 
166
168
  # @see ObjectWithIoIvars
169
+ # rubocop:disable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
167
170
  class ObjectWithNestedIoIvars
168
171
  def initialize
169
172
  @bish = ObjectWithIoIvars.new
@@ -174,6 +177,7 @@ RSpec.describe Airbrake::Notice do
174
177
  raise NotImplementedError
175
178
  end
176
179
  end
180
+ # rubocop:enable Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration
177
181
 
178
182
  context "and also when it's a closed Tempfile" do
179
183
  it "doesn't fail" do
@@ -268,7 +272,7 @@ RSpec.describe Airbrake::Notice do
268
272
  it "sets a payload value" do
269
273
  hash = { bingo: 'bango' }
270
274
  notice[:params] = hash
271
- expect(notice[:params]).to equal(hash)
275
+ expect(notice[:params]).to eq(hash)
272
276
  end
273
277
 
274
278
  it "raises error if notice is ignored" do
@@ -3,21 +3,9 @@ RSpec.describe Airbrake::PerformanceBreakdown do
3
3
  subject do
4
4
  described_class.new(
5
5
  method: 'GET', route: '/', response_type: '', groups: {},
6
- start_time: Time.now
7
6
  )
8
7
  end
9
8
 
10
9
  it { is_expected.to respond_to(:stash) }
11
10
  end
12
-
13
- describe "#end_time" do
14
- it "is always equal to start_time + 1 second by default" do
15
- time = Time.now
16
- performance_breakdown = described_class.new(
17
- method: 'GET', route: '/', response_type: '', groups: {},
18
- start_time: time
19
- )
20
- expect(performance_breakdown.end_time).to eq(time + 1)
21
- end
22
- end
23
11
  end