airbrake-ruby 4.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/lib/airbrake-ruby.rb +515 -0
  3. data/lib/airbrake-ruby/async_sender.rb +80 -0
  4. data/lib/airbrake-ruby/backtrace.rb +196 -0
  5. data/lib/airbrake-ruby/benchmark.rb +39 -0
  6. data/lib/airbrake-ruby/code_hunk.rb +51 -0
  7. data/lib/airbrake-ruby/config.rb +229 -0
  8. data/lib/airbrake-ruby/config/validator.rb +91 -0
  9. data/lib/airbrake-ruby/deploy_notifier.rb +36 -0
  10. data/lib/airbrake-ruby/file_cache.rb +54 -0
  11. data/lib/airbrake-ruby/filter_chain.rb +95 -0
  12. data/lib/airbrake-ruby/filters/context_filter.rb +29 -0
  13. data/lib/airbrake-ruby/filters/dependency_filter.rb +31 -0
  14. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +46 -0
  15. data/lib/airbrake-ruby/filters/gem_root_filter.rb +33 -0
  16. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +92 -0
  17. data/lib/airbrake-ruby/filters/git_repository_filter.rb +64 -0
  18. data/lib/airbrake-ruby/filters/git_revision_filter.rb +66 -0
  19. data/lib/airbrake-ruby/filters/keys_blacklist.rb +49 -0
  20. data/lib/airbrake-ruby/filters/keys_filter.rb +140 -0
  21. data/lib/airbrake-ruby/filters/keys_whitelist.rb +48 -0
  22. data/lib/airbrake-ruby/filters/root_directory_filter.rb +28 -0
  23. data/lib/airbrake-ruby/filters/sql_filter.rb +128 -0
  24. data/lib/airbrake-ruby/filters/system_exit_filter.rb +23 -0
  25. data/lib/airbrake-ruby/filters/thread_filter.rb +92 -0
  26. data/lib/airbrake-ruby/hash_keyable.rb +37 -0
  27. data/lib/airbrake-ruby/ignorable.rb +44 -0
  28. data/lib/airbrake-ruby/inspectable.rb +39 -0
  29. data/lib/airbrake-ruby/loggable.rb +34 -0
  30. data/lib/airbrake-ruby/monotonic_time.rb +43 -0
  31. data/lib/airbrake-ruby/nested_exception.rb +38 -0
  32. data/lib/airbrake-ruby/notice.rb +162 -0
  33. data/lib/airbrake-ruby/notice_notifier.rb +134 -0
  34. data/lib/airbrake-ruby/performance_breakdown.rb +46 -0
  35. data/lib/airbrake-ruby/performance_notifier.rb +155 -0
  36. data/lib/airbrake-ruby/promise.rb +109 -0
  37. data/lib/airbrake-ruby/query.rb +54 -0
  38. data/lib/airbrake-ruby/request.rb +46 -0
  39. data/lib/airbrake-ruby/response.rb +74 -0
  40. data/lib/airbrake-ruby/stashable.rb +15 -0
  41. data/lib/airbrake-ruby/stat.rb +73 -0
  42. data/lib/airbrake-ruby/sync_sender.rb +113 -0
  43. data/lib/airbrake-ruby/tdigest.rb +393 -0
  44. data/lib/airbrake-ruby/thread_pool.rb +128 -0
  45. data/lib/airbrake-ruby/time_truncate.rb +17 -0
  46. data/lib/airbrake-ruby/timed_trace.rb +58 -0
  47. data/lib/airbrake-ruby/truncator.rb +115 -0
  48. data/lib/airbrake-ruby/version.rb +6 -0
  49. data/spec/airbrake_spec.rb +324 -0
  50. data/spec/async_sender_spec.rb +72 -0
  51. data/spec/backtrace_spec.rb +427 -0
  52. data/spec/benchmark_spec.rb +33 -0
  53. data/spec/code_hunk_spec.rb +115 -0
  54. data/spec/config/validator_spec.rb +184 -0
  55. data/spec/config_spec.rb +154 -0
  56. data/spec/deploy_notifier_spec.rb +48 -0
  57. data/spec/file_cache_spec.rb +34 -0
  58. data/spec/filter_chain_spec.rb +92 -0
  59. data/spec/filters/context_filter_spec.rb +23 -0
  60. data/spec/filters/dependency_filter_spec.rb +12 -0
  61. data/spec/filters/exception_attributes_filter_spec.rb +50 -0
  62. data/spec/filters/gem_root_filter_spec.rb +41 -0
  63. data/spec/filters/git_last_checkout_filter_spec.rb +46 -0
  64. data/spec/filters/git_repository_filter.rb +61 -0
  65. data/spec/filters/git_revision_filter_spec.rb +126 -0
  66. data/spec/filters/keys_blacklist_spec.rb +225 -0
  67. data/spec/filters/keys_whitelist_spec.rb +194 -0
  68. data/spec/filters/root_directory_filter_spec.rb +39 -0
  69. data/spec/filters/sql_filter_spec.rb +276 -0
  70. data/spec/filters/system_exit_filter_spec.rb +14 -0
  71. data/spec/filters/thread_filter_spec.rb +277 -0
  72. data/spec/fixtures/notroot.txt +7 -0
  73. data/spec/fixtures/project_root/code.rb +221 -0
  74. data/spec/fixtures/project_root/empty_file.rb +0 -0
  75. data/spec/fixtures/project_root/long_line.txt +1 -0
  76. data/spec/fixtures/project_root/short_file.rb +3 -0
  77. data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +5 -0
  78. data/spec/helpers.rb +9 -0
  79. data/spec/ignorable_spec.rb +14 -0
  80. data/spec/inspectable_spec.rb +45 -0
  81. data/spec/monotonic_time_spec.rb +12 -0
  82. data/spec/nested_exception_spec.rb +73 -0
  83. data/spec/notice_notifier/options_spec.rb +259 -0
  84. data/spec/notice_notifier_spec.rb +356 -0
  85. data/spec/notice_spec.rb +296 -0
  86. data/spec/performance_breakdown_spec.rb +12 -0
  87. data/spec/performance_notifier_spec.rb +491 -0
  88. data/spec/promise_spec.rb +197 -0
  89. data/spec/query_spec.rb +11 -0
  90. data/spec/request_spec.rb +11 -0
  91. data/spec/response_spec.rb +88 -0
  92. data/spec/spec_helper.rb +100 -0
  93. data/spec/stashable_spec.rb +23 -0
  94. data/spec/stat_spec.rb +47 -0
  95. data/spec/sync_sender_spec.rb +133 -0
  96. data/spec/tdigest_spec.rb +230 -0
  97. data/spec/thread_pool_spec.rb +158 -0
  98. data/spec/time_truncate_spec.rb +13 -0
  99. data/spec/timed_trace_spec.rb +125 -0
  100. data/spec/truncator_spec.rb +238 -0
  101. metadata +216 -0
@@ -0,0 +1,12 @@
1
+ RSpec.describe Airbrake::PerformanceBreakdown do
2
+ describe "#stash" do
3
+ subject do
4
+ described_class.new(
5
+ method: 'GET', route: '/', response_type: '', groups: {},
6
+ start_time: Time.now
7
+ )
8
+ end
9
+
10
+ it { is_expected.to respond_to(:stash) }
11
+ end
12
+ end
@@ -0,0 +1,491 @@
1
+ RSpec.describe Airbrake::PerformanceNotifier do
2
+ let(:routes) { 'https://api.airbrake.io/api/v5/projects/1/routes-stats' }
3
+ let(:queries) { 'https://api.airbrake.io/api/v5/projects/1/queries-stats' }
4
+ let(:breakdowns) { 'https://api.airbrake.io/api/v5/projects/1/routes-breakdowns' }
5
+
6
+ before do
7
+ stub_request(:put, routes).to_return(status: 200, body: '')
8
+ stub_request(:put, queries).to_return(status: 200, body: '')
9
+ stub_request(:put, breakdowns).to_return(status: 200, body: '')
10
+
11
+ Airbrake::Config.instance = Airbrake::Config.new(
12
+ project_id: 1,
13
+ project_key: 'banana',
14
+ performance_stats: true,
15
+ performance_stats_flush_period: 0,
16
+ query_stats: true
17
+ )
18
+ end
19
+
20
+ describe "#notify" do
21
+ it "sends full query" do
22
+ subject.notify(
23
+ Airbrake::Query.new(
24
+ method: 'POST',
25
+ route: '/foo',
26
+ query: 'SELECT * FROM things',
27
+ func: 'foo',
28
+ file: 'foo.rb',
29
+ line: 123,
30
+ start_time: Time.new(2018, 1, 1, 0, 49, 0, 0),
31
+ end_time: Time.new(2018, 1, 1, 0, 50, 0, 0)
32
+ )
33
+ )
34
+ subject.close
35
+
36
+ expect(
37
+ a_request(:put, queries).with(body: %r|
38
+ \A{"queries":\[{
39
+ "method":"POST",
40
+ "route":"/foo",
41
+ "query":"SELECT\s\*\sFROM\sthings",
42
+ "time":"2018-01-01T00:49:00\+00:00",
43
+ "function":"foo",
44
+ "file":"foo.rb",
45
+ "line":123,
46
+ "count":1,
47
+ "sum":60000.0,
48
+ "sumsq":3600000000.0,
49
+ "tdigest":"AAAAAkA0AAAAAAAAAAAAAUdqYAAB"
50
+ }\]}\z|x)
51
+ ).to have_been_made
52
+ end
53
+
54
+ it "sends full request" do
55
+ subject.notify(
56
+ Airbrake::Request.new(
57
+ method: 'POST',
58
+ route: '/foo',
59
+ status_code: 200,
60
+ start_time: Time.new(2018, 1, 1, 0, 49, 0, 0),
61
+ end_time: Time.new(2018, 1, 1, 0, 50, 0, 0)
62
+ )
63
+ )
64
+ subject.close
65
+
66
+ expect(
67
+ a_request(:put, routes).with(body: %r|
68
+ \A{"routes":\[{
69
+ "method":"POST",
70
+ "route":"/foo",
71
+ "statusCode":200,
72
+ "time":"2018-01-01T00:49:00\+00:00",
73
+ "count":1,
74
+ "sum":60000.0,
75
+ "sumsq":3600000000.0,
76
+ "tdigest":"AAAAAkA0AAAAAAAAAAAAAUdqYAAB"
77
+ }\]}\z|x)
78
+ ).to have_been_made
79
+ end
80
+
81
+ it "sends full performance breakdown" do
82
+ subject.notify(
83
+ Airbrake::PerformanceBreakdown.new(
84
+ method: 'DELETE',
85
+ route: '/routes-breakdowns',
86
+ response_type: 'json',
87
+ start_time: Time.new(2018, 1, 1, 0, 49, 0, 0),
88
+ end_time: Time.new(2018, 1, 1, 0, 50, 0, 0),
89
+ groups: { db: 131, view: 421 }
90
+ )
91
+ )
92
+ subject.close
93
+
94
+ expect(
95
+ a_request(:put, breakdowns).with(body: %r|
96
+ \A{"routes":\[{
97
+ "method":"DELETE",
98
+ "route":"/routes-breakdowns",
99
+ "responseType":"json",
100
+ "time":"2018-01-01T00:49:00\+00:00",
101
+ "count":1,
102
+ "sum":60000.0,
103
+ "sumsq":3600000000.0,
104
+ "tdigest":"AAAAAkA0AAAAAAAAAAAAAUdqYAAB",
105
+ "groups":{
106
+ "db":{
107
+ "count":1,
108
+ "sum":131.0,
109
+ "sumsq":17161.0,
110
+ "tdigest":"AAAAAkA0AAAAAAAAAAAAAUMDAAAB"
111
+ },
112
+ "view":{
113
+ "count":1,
114
+ "sum":421.0,
115
+ "sumsq":177241.0,
116
+ "tdigest":"AAAAAkA0AAAAAAAAAAAAAUPSgAAB"
117
+ }
118
+ }
119
+ }\]}\z|x)
120
+ ).to have_been_made
121
+ end
122
+
123
+ it "rounds time to the floor minute" do
124
+ subject.notify(
125
+ Airbrake::Request.new(
126
+ method: 'GET',
127
+ route: '/foo',
128
+ status_code: 200,
129
+ start_time: Time.new(2018, 1, 1, 0, 0, 20, 0)
130
+ )
131
+ )
132
+ subject.close
133
+
134
+ expect(
135
+ a_request(:put, routes).with(body: /"time":"2018-01-01T00:00:00\+00:00"/)
136
+ ).to have_been_made
137
+ end
138
+
139
+ it "increments routes with the same key" do
140
+ subject.notify(
141
+ Airbrake::Request.new(
142
+ method: 'GET',
143
+ route: '/foo',
144
+ status_code: 200,
145
+ start_time: Time.new(2018, 1, 1, 0, 0, 20, 0)
146
+ )
147
+ )
148
+ subject.notify(
149
+ Airbrake::Request.new(
150
+ method: 'GET',
151
+ route: '/foo',
152
+ status_code: 200,
153
+ start_time: Time.new(2018, 1, 1, 0, 0, 50, 0)
154
+ )
155
+ )
156
+ subject.close
157
+
158
+ expect(
159
+ a_request(:put, routes).with(body: /"count":2/)
160
+ ).to have_been_made
161
+ end
162
+
163
+ it "groups routes by time" do
164
+ subject.notify(
165
+ Airbrake::Request.new(
166
+ method: 'GET',
167
+ route: '/foo',
168
+ status_code: 200,
169
+ start_time: Time.new(2018, 1, 1, 0, 0, 49, 0),
170
+ end_time: Time.new(2018, 1, 1, 0, 0, 50, 0)
171
+ )
172
+ )
173
+ subject.notify(
174
+ Airbrake::Request.new(
175
+ method: 'GET',
176
+ route: '/foo',
177
+ status_code: 200,
178
+ start_time: Time.new(2018, 1, 1, 0, 1, 49, 0),
179
+ end_time: Time.new(2018, 1, 1, 0, 1, 55, 0)
180
+ )
181
+ )
182
+ subject.close
183
+
184
+ expect(
185
+ a_request(:put, routes).with(
186
+ body: %r|\A
187
+ {"routes":\[
188
+ {"method":"GET","route":"/foo","statusCode":200,
189
+ "time":"2018-01-01T00:00:00\+00:00","count":1,"sum":1000.0,
190
+ "sumsq":1000000.0,"tdigest":"AAAAAkA0AAAAAAAAAAAAAUR6AAAB"},
191
+ {"method":"GET","route":"/foo","statusCode":200,
192
+ "time":"2018-01-01T00:01:00\+00:00","count":1,"sum":6000.0,
193
+ "sumsq":36000000.0,"tdigest":"AAAAAkA0AAAAAAAAAAAAAUW7gAAB"}\]}
194
+ \z|x
195
+ )
196
+ ).to have_been_made
197
+ end
198
+
199
+ it "groups routes by route key" do
200
+ subject.notify(
201
+ Airbrake::Request.new(
202
+ method: 'GET',
203
+ route: '/foo',
204
+ status_code: 200,
205
+ start_time: Time.new(2018, 1, 1, 0, 49, 0, 0),
206
+ end_time: Time.new(2018, 1, 1, 0, 50, 0, 0)
207
+ )
208
+ )
209
+ subject.notify(
210
+ Airbrake::Request.new(
211
+ method: 'POST',
212
+ route: '/foo',
213
+ status_code: 200,
214
+ start_time: Time.new(2018, 1, 1, 0, 49, 0, 0),
215
+ end_time: Time.new(2018, 1, 1, 0, 50, 0, 0)
216
+ )
217
+ )
218
+ subject.close
219
+
220
+ expect(
221
+ a_request(:put, routes).with(
222
+ body: %r|\A
223
+ {"routes":\[
224
+ {"method":"GET","route":"/foo","statusCode":200,
225
+ "time":"2018-01-01T00:49:00\+00:00","count":1,"sum":60000.0,
226
+ "sumsq":3600000000.0,"tdigest":"AAAAAkA0AAAAAAAAAAAAAUdqYAAB"},
227
+ {"method":"POST","route":"/foo","statusCode":200,
228
+ "time":"2018-01-01T00:49:00\+00:00","count":1,"sum":60000.0,
229
+ "sumsq":3600000000.0,"tdigest":"AAAAAkA0AAAAAAAAAAAAAUdqYAAB"}\]}
230
+ \z|x
231
+ )
232
+ ).to have_been_made
233
+ end
234
+
235
+ it "groups performance breakdowns by route key" do
236
+ subject.notify(
237
+ Airbrake::PerformanceBreakdown.new(
238
+ method: 'DELETE',
239
+ route: '/routes-breakdowns',
240
+ response_type: 'json',
241
+ start_time: Time.new(2018, 1, 1, 0, 0, 20, 0),
242
+ end_time: Time.new(2018, 1, 1, 0, 0, 22, 0),
243
+ groups: { db: 131, view: 421 }
244
+ )
245
+ )
246
+ subject.notify(
247
+ Airbrake::PerformanceBreakdown.new(
248
+ method: 'DELETE',
249
+ route: '/routes-breakdowns',
250
+ response_type: 'json',
251
+ start_time: Time.new(2018, 1, 1, 0, 0, 30, 0),
252
+ end_time: Time.new(2018, 1, 1, 0, 0, 32, 0),
253
+ groups: { db: 55, view: 11 }
254
+ )
255
+ )
256
+ subject.close
257
+
258
+ expect(
259
+ a_request(:put, breakdowns).with(body: %r|
260
+ \A{"routes":\[{
261
+ "method":"DELETE",
262
+ "route":"/routes-breakdowns",
263
+ "responseType":"json",
264
+ "time":"2018-01-01T00:00:00\+00:00",
265
+ "count":2,
266
+ "sum":4000.0,
267
+ "sumsq":8000000.0,
268
+ "tdigest":"AAAAAkA0AAAAAAAAAAAAAUT6AAAC",
269
+ "groups":{
270
+ "db":{
271
+ "count":2,
272
+ "sum":186.0,
273
+ "sumsq":20186.0,
274
+ "tdigest":"AAAAAkA0AAAAAAAAAAAAAkJcAABCmAAAAQE="
275
+ },
276
+ "view":{
277
+ "count":2,
278
+ "sum":432.0,
279
+ "sumsq":177362.0,
280
+ "tdigest":"AAAAAkA0AAAAAAAAAAAAAkEwAABDzQAAAQE="
281
+ }
282
+ }
283
+ }\]}\z|x)
284
+ ).to have_been_made
285
+ end
286
+
287
+ it "returns a promise" do
288
+ promise = subject.notify(
289
+ Airbrake::Request.new(
290
+ method: 'GET',
291
+ route: '/foo',
292
+ status_code: 200,
293
+ start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
294
+ )
295
+ )
296
+ subject.close
297
+
298
+ expect(promise).to be_an(Airbrake::Promise)
299
+ expect(promise.value).to eq('' => nil)
300
+ end
301
+
302
+ it "checks performance stat configuration" do
303
+ request = Airbrake::Request.new(
304
+ method: 'GET', route: '/foo', status_code: 200, start_time: Time.new
305
+ )
306
+ expect(Airbrake::Config.instance).to receive(:check_performance_options)
307
+ .with(request).and_return(Airbrake::Promise.new)
308
+ subject.notify(request)
309
+ subject.close
310
+ end
311
+
312
+ it "sends environment when it's specified" do
313
+ Airbrake::Config.instance.merge(performance_stats: true, environment: 'test')
314
+
315
+ subject.notify(
316
+ Airbrake::Request.new(
317
+ method: 'POST',
318
+ route: '/foo',
319
+ status_code: 200,
320
+ start_time: Time.new
321
+ )
322
+ )
323
+ subject.close
324
+
325
+ expect(
326
+ a_request(:put, routes).with(
327
+ body: /\A{"routes":\[.+\],"environment":"test"}\z/x
328
+ )
329
+ ).to have_been_made
330
+ end
331
+
332
+ context "when config is invalid" do
333
+ before { Airbrake::Config.instance.merge(project_id: nil) }
334
+
335
+ it "returns a rejected promise" do
336
+ promise = subject.notify({})
337
+ expect(promise).to be_rejected
338
+ end
339
+ end
340
+
341
+ describe "payload grouping" do
342
+ let(:flush_period) { 0.5 }
343
+
344
+ it "groups payload by performance name and sends it separately" do
345
+ Airbrake::Config.instance.merge(
346
+ project_id: 1,
347
+ project_key: 'banana',
348
+ performance_stats: true,
349
+ performance_stats_flush_period: flush_period
350
+ )
351
+
352
+ subject.notify(
353
+ Airbrake::Request.new(
354
+ method: 'GET',
355
+ route: '/foo',
356
+ status_code: 200,
357
+ start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
358
+ )
359
+ )
360
+
361
+ subject.notify(
362
+ Airbrake::Query.new(
363
+ method: 'POST',
364
+ route: '/foo',
365
+ query: 'SELECT * FROM things',
366
+ start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
367
+ )
368
+ )
369
+
370
+ sleep(flush_period + 0.5)
371
+
372
+ expect(a_request(:put, routes)).to have_been_made
373
+ expect(a_request(:put, queries)).to have_been_made
374
+ end
375
+ end
376
+
377
+ context "when an ignore filter was defined" do
378
+ before { subject.add_filter(&:ignore!) }
379
+
380
+ it "doesn't notify airbrake of requests" do
381
+ subject.notify(
382
+ Airbrake::Request.new(
383
+ method: 'GET',
384
+ route: '/foo',
385
+ status_code: 200,
386
+ start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
387
+ )
388
+ )
389
+ subject.close
390
+
391
+ expect(a_request(:put, routes)).not_to have_been_made
392
+ end
393
+
394
+ it "doesn't notify airbrake of queries" do
395
+ subject.notify(
396
+ Airbrake::Query.new(
397
+ method: 'POST',
398
+ route: '/foo',
399
+ query: 'SELECT * FROM things',
400
+ start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
401
+ )
402
+ )
403
+ subject.close
404
+
405
+ expect(a_request(:put, queries)).not_to have_been_made
406
+ end
407
+ end
408
+
409
+ context "when a filter that modifies payload was defined" do
410
+ before do
411
+ subject.add_filter do |resource|
412
+ resource.route = '[Filtered]'
413
+ end
414
+ end
415
+
416
+ it "notifies airbrake with modified payload" do
417
+ subject.notify(
418
+ Airbrake::Query.new(
419
+ method: 'POST',
420
+ route: '/foo',
421
+ query: 'SELECT * FROM things',
422
+ start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
423
+ )
424
+ )
425
+ subject.close
426
+
427
+ expect(
428
+ a_request(:put, queries).with(
429
+ body: /\A{"queries":\[{"method":"POST","route":"\[Filtered\]"/
430
+ )
431
+ ).to have_been_made
432
+ end
433
+ end
434
+ end
435
+
436
+ describe "#close" do
437
+ before do
438
+ Airbrake::Config.instance.merge(performance_stats_flush_period: 0.1)
439
+ end
440
+
441
+ after do
442
+ Airbrake::Config.instance.merge(performance_stats_flush_period: 0)
443
+ end
444
+
445
+ it "kills the background thread" do
446
+ expect_any_instance_of(Thread).to receive(:kill).and_call_original
447
+ subject.notify(
448
+ Airbrake::Query.new(
449
+ method: 'POST',
450
+ route: '/foo',
451
+ query: 'SELECT * FROM things',
452
+ start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
453
+ )
454
+ )
455
+ subject.close
456
+ end
457
+
458
+ it "logs the exit message" do
459
+ allow(Airbrake::Loggable.instance).to receive(:debug)
460
+ expect(Airbrake::Loggable.instance).to receive(:debug).with(
461
+ /performance notifier closed/
462
+ )
463
+ subject.close
464
+ end
465
+ end
466
+
467
+ describe "#delete_filter" do
468
+ let(:filter) do
469
+ Class.new do
470
+ def call(resource); end
471
+ end
472
+ end
473
+
474
+ before { subject.add_filter(filter.new) }
475
+
476
+ it "deletes a filter" do
477
+ subject.delete_filter(filter)
478
+ subject.notify(
479
+ Airbrake::Request.new(
480
+ method: 'POST',
481
+ route: '/foo',
482
+ status_code: 200,
483
+ start_time: Time.new(2018, 1, 1, 0, 49, 0, 0)
484
+ )
485
+ )
486
+ subject.close
487
+
488
+ expect(a_request(:put, routes)).to have_been_made
489
+ end
490
+ end
491
+ end