airbrake-ruby 4.7.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 (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 +125 -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 +262 -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