airbrake-ruby 5.0.1 → 5.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +4 -2
  3. data/lib/airbrake-ruby/async_sender.rb +3 -1
  4. data/lib/airbrake-ruby/config.rb +15 -6
  5. data/lib/airbrake-ruby/config/processor.rb +7 -20
  6. data/lib/airbrake-ruby/context.rb +51 -0
  7. data/lib/airbrake-ruby/file_cache.rb +1 -1
  8. data/lib/airbrake-ruby/filter_chain.rb +2 -0
  9. data/lib/airbrake-ruby/filters/context_filter.rb +4 -5
  10. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +1 -1
  11. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +1 -1
  12. data/lib/airbrake-ruby/filters/git_revision_filter.rb +1 -1
  13. data/lib/airbrake-ruby/filters/keys_filter.rb +2 -2
  14. data/lib/airbrake-ruby/filters/sql_filter.rb +2 -2
  15. data/lib/airbrake-ruby/filters/thread_filter.rb +2 -3
  16. data/lib/airbrake-ruby/grouppable.rb +1 -1
  17. data/lib/airbrake-ruby/ignorable.rb +0 -2
  18. data/lib/airbrake-ruby/mergeable.rb +1 -1
  19. data/lib/airbrake-ruby/notice_notifier.rb +3 -4
  20. data/lib/airbrake-ruby/performance_notifier.rb +1 -2
  21. data/lib/airbrake-ruby/remote_settings.rb +10 -50
  22. data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
  23. data/lib/airbrake-ruby/remote_settings/settings_data.rb +3 -8
  24. data/lib/airbrake-ruby/tdigest.rb +7 -6
  25. data/lib/airbrake-ruby/thread_pool.rb +5 -3
  26. data/lib/airbrake-ruby/timed_trace.rb +1 -3
  27. data/lib/airbrake-ruby/version.rb +2 -2
  28. data/spec/airbrake_spec.rb +139 -76
  29. data/spec/async_sender_spec.rb +10 -8
  30. data/spec/backtrace_spec.rb +13 -10
  31. data/spec/benchmark_spec.rb +5 -3
  32. data/spec/code_hunk_spec.rb +24 -15
  33. data/spec/config/processor_spec.rb +29 -87
  34. data/spec/config/validator_spec.rb +5 -2
  35. data/spec/config_spec.rb +26 -17
  36. data/spec/context_spec.rb +54 -0
  37. data/spec/deploy_notifier_spec.rb +6 -4
  38. data/spec/file_cache_spec.rb +1 -0
  39. data/spec/filter_chain_spec.rb +29 -24
  40. data/spec/filters/context_filter_spec.rb +14 -5
  41. data/spec/filters/dependency_filter_spec.rb +3 -1
  42. data/spec/filters/exception_attributes_filter_spec.rb +5 -3
  43. data/spec/filters/gem_root_filter_spec.rb +5 -2
  44. data/spec/filters/git_last_checkout_filter_spec.rb +10 -12
  45. data/spec/filters/git_repository_filter.rb +9 -9
  46. data/spec/filters/git_revision_filter_spec.rb +19 -19
  47. data/spec/filters/keys_allowlist_spec.rb +25 -16
  48. data/spec/filters/keys_blocklist_spec.rb +25 -18
  49. data/spec/filters/root_directory_filter_spec.rb +3 -3
  50. data/spec/filters/sql_filter_spec.rb +26 -26
  51. data/spec/filters/system_exit_filter_spec.rb +4 -2
  52. data/spec/filters/thread_filter_spec.rb +16 -14
  53. data/spec/loggable_spec.rb +2 -2
  54. data/spec/monotonic_time_spec.rb +8 -6
  55. data/spec/nested_exception_spec.rb +46 -46
  56. data/spec/notice_notifier/options_spec.rb +23 -13
  57. data/spec/notice_notifier_spec.rb +52 -47
  58. data/spec/notice_spec.rb +6 -2
  59. data/spec/performance_notifier_spec.rb +67 -60
  60. data/spec/promise_spec.rb +38 -32
  61. data/spec/remote_settings/callback_spec.rb +162 -0
  62. data/spec/remote_settings/settings_data_spec.rb +23 -53
  63. data/spec/remote_settings_spec.rb +42 -75
  64. data/spec/response_spec.rb +34 -12
  65. data/spec/stashable_spec.rb +5 -5
  66. data/spec/stat_spec.rb +7 -5
  67. data/spec/sync_sender_spec.rb +49 -16
  68. data/spec/tdigest_spec.rb +61 -56
  69. data/spec/thread_pool_spec.rb +65 -55
  70. data/spec/time_truncate_spec.rb +4 -2
  71. data/spec/timed_trace_spec.rb +32 -30
  72. data/spec/truncator_spec.rb +72 -43
  73. metadata +53 -47
@@ -22,72 +22,35 @@ RSpec.describe Airbrake::RemoteSettings do
22
22
  }
23
23
  end
24
24
 
25
- let(:config_path) { described_class::CONFIG_DUMP_PATH }
26
- let(:config_dir) { File.dirname(config_path) }
27
-
28
25
  let!(:stub) do
29
26
  stub_request(:get, Regexp.new(endpoint))
30
27
  .to_return(status: 200, body: body.to_json)
31
28
  end
32
29
 
33
- before do
34
- # Do not create config dumps on disk.
35
- allow(Dir).to receive(:mkdir).with(config_dir)
36
- allow(File).to receive(:write).with(config_path, anything)
37
- end
38
-
39
30
  describe ".poll" do
40
31
  describe "config loading" do
41
32
  let(:settings_data) { described_class::SettingsData.new(project_id, body) }
42
33
 
43
34
  before do
44
- allow(File).to receive(:exist?).with(config_path).and_return(true)
45
- allow(File).to receive(:read).with(config_path).and_return(body.to_json)
46
-
47
35
  allow(described_class::SettingsData).to receive(:new).and_return(settings_data)
48
36
  end
49
37
 
50
- it "loads the config from disk" do
51
- expect(File).to receive(:read).with(config_path)
52
- expect(settings_data).to receive(:merge!).with(body).twice
53
-
54
- remote_settings = described_class.poll(project_id, host) {}
55
- sleep(0.2)
56
- remote_settings.stop_polling
57
-
58
- expect(stub).to have_been_requested.once
59
- end
60
-
61
38
  it "yields the config to the block twice" do
62
39
  block = proc {}
63
- expect(block).to receive(:call).twice
40
+ allow(block).to receive(:call)
64
41
 
65
42
  remote_settings = described_class.poll(project_id, host, &block)
66
43
  sleep(0.2)
67
44
  remote_settings.stop_polling
68
45
 
69
46
  expect(stub).to have_been_requested.once
70
- end
71
-
72
- context "when config loading fails" do
73
- it "logs an error" do
74
- expect(File).to receive(:read).and_raise(StandardError)
75
- expect(Airbrake::Loggable.instance).to receive(:error).with(
76
- '**Airbrake: config loading failed: StandardError',
77
- )
78
-
79
- remote_settings = described_class.poll(project_id, host) {}
80
- sleep(0.2)
81
- remote_settings.stop_polling
82
-
83
- expect(stub).to have_been_requested.once
84
- end
47
+ expect(block).to have_received(:call).twice
85
48
  end
86
49
  end
87
50
 
88
51
  context "when no errors are raised" do
89
52
  it "makes a request to AWS S3" do
90
- remote_settings = described_class.poll(project_id, host) {}
53
+ remote_settings = described_class.poll(project_id, host) { anything }
91
54
  sleep(0.1)
92
55
  remote_settings.stop_polling
93
56
 
@@ -95,7 +58,7 @@ RSpec.describe Airbrake::RemoteSettings do
95
58
  end
96
59
 
97
60
  it "sends params about the environment with the request" do
98
- remote_settings = described_class.poll(project_id, host) {}
61
+ remote_settings = described_class.poll(project_id, host) { anything }
99
62
  sleep(0.1)
100
63
  remote_settings.stop_polling
101
64
 
@@ -105,6 +68,7 @@ RSpec.describe Airbrake::RemoteSettings do
105
68
  expect(stub_with_query_params).to have_been_requested.at_least_once
106
69
  end
107
70
 
71
+ # rubocop:disable RSpec/MultipleExpectations
108
72
  it "fetches remote settings" do
109
73
  settings = nil
110
74
  remote_settings = described_class.poll(project_id, host) do |data|
@@ -117,11 +81,12 @@ RSpec.describe Airbrake::RemoteSettings do
117
81
  expect(settings.performance_stats?).to eq(false)
118
82
  expect(settings.interval).to eq(1)
119
83
  end
84
+ # rubocop:enable RSpec/MultipleExpectations
120
85
  end
121
86
 
122
87
  context "when an error is raised while making a HTTP request" do
123
88
  before do
124
- allow(Net::HTTP).to receive(:get).and_raise(StandardError)
89
+ allow(Net::HTTP).to receive(:get_response).and_raise(StandardError)
125
90
  end
126
91
 
127
92
  it "doesn't fetch remote settings" do
@@ -155,10 +120,10 @@ RSpec.describe Airbrake::RemoteSettings do
155
120
  end
156
121
  end
157
122
 
158
- context "when API returns an XML response" do
123
+ context "when API returns a non-200 response" do
159
124
  let!(:stub) do
160
125
  stub_request(:get, Regexp.new(endpoint))
161
- .to_return(status: 200, body: '<?xml ...')
126
+ .to_return(status: 201, body: body.to_json)
162
127
  end
163
128
 
164
129
  it "doesn't update settings data" do
@@ -172,59 +137,61 @@ RSpec.describe Airbrake::RemoteSettings do
172
137
  expect(stub).to have_been_requested.once
173
138
  expect(settings.interval).to eq(600)
174
139
  end
175
- end
176
140
 
177
- context "when a config route is specified in the returned data" do
178
- let(:new_endpoint) do
179
- "http://example.com"
180
- end
141
+ it "logs error" do
142
+ allow(Airbrake::Loggable.instance).to receive(:error)
181
143
 
182
- let(:body) do
183
- { 'config_route' => new_endpoint, 'poll_sec' => 0.1 }
144
+ remote_settings = described_class.poll(project_id, host) { anything }
145
+ sleep(0.1)
146
+ remote_settings.stop_polling
147
+
148
+ expect(Airbrake::Loggable.instance).to have_received(:error).with(body.to_json)
184
149
  end
150
+ end
185
151
 
186
- let!(:new_stub) do
187
- stub_request(:get, Regexp.new(new_endpoint))
152
+ context "when API returns a 200 response" do
153
+ let!(:stub) do
154
+ stub_request(:get, Regexp.new(endpoint))
188
155
  .to_return(status: 200, body: body.to_json)
189
156
  end
190
157
 
191
- it "makes the next request to the specified config route" do
192
- remote_settings = described_class.poll(project_id, host) {}
193
- sleep(0.2)
158
+ it "doesn't log errors" do
159
+ allow(Airbrake::Loggable.instance).to receive(:error)
194
160
 
161
+ remote_settings = described_class.poll(project_id, host) { anything }
162
+ sleep(0.1)
195
163
  remote_settings.stop_polling
196
164
 
165
+ expect(Airbrake::Loggable.instance).not_to have_received(:error)
197
166
  expect(stub).to have_been_requested.once
198
- expect(new_stub).to have_been_requested.once
199
167
  end
200
168
  end
201
- end
202
169
 
203
- describe "#stop_polling" do
204
- it "dumps config data to disk" do
205
- expect(Dir).to receive(:mkdir).with(config_dir)
206
- expect(File).to receive(:write).with(config_path, body.to_json)
207
-
208
- remote_settings = described_class.poll(project_id, host) {}
209
- sleep(0.2)
210
- remote_settings.stop_polling
170
+ # rubocop:disable RSpec/MultipleMemoizedHelpers
171
+ context "when a config route is specified in the returned data" do
172
+ let(:new_config_route) do
173
+ '213/config/111/config.json'
174
+ end
211
175
 
212
- expect(stub).to have_been_requested.once
213
- end
176
+ let(:body) do
177
+ { 'config_route' => new_config_route, 'poll_sec' => 0.1 }
178
+ end
214
179
 
215
- context "when config dumping fails" do
216
- it "logs an error" do
217
- expect(File).to receive(:write).and_raise(StandardError)
218
- expect(Airbrake::Loggable.instance).to receive(:error).with(
219
- '**Airbrake: config dumping failed: StandardError',
220
- )
180
+ let!(:new_stub) do
181
+ stub_request(:get, Regexp.new(new_config_route))
182
+ .to_return(status: 200, body: body.to_json)
183
+ end
221
184
 
222
- remote_settings = described_class.poll(project_id, host) {}
185
+ it "makes the next request to the specified config route" do
186
+ remote_settings = described_class.poll(project_id, host) { anything }
223
187
  sleep(0.2)
188
+
224
189
  remote_settings.stop_polling
225
190
 
226
191
  expect(stub).to have_been_requested.once
192
+ expect(new_stub).to have_been_requested.once
227
193
  end
228
194
  end
195
+ # rubocop:enable RSpec/MultipleMemoizedHelpers
229
196
  end
230
197
  end
@@ -2,24 +2,34 @@ RSpec.describe Airbrake::Response do
2
2
  describe ".parse" do
3
3
  [200, 201, 204].each do |code|
4
4
  context "when response code is #{code}" do
5
+ before do
6
+ allow(Airbrake::Loggable.instance).to receive(:debug)
7
+ end
8
+
5
9
  it "logs response body" do
6
- expect(Airbrake::Loggable.instance).to receive(:debug).with(
10
+ described_class.parse(OpenStruct.new(code: code, body: '{}'))
11
+
12
+ expect(Airbrake::Loggable.instance).to have_received(:debug).with(
7
13
  /Airbrake::Response \(#{code}\): {}/,
8
14
  )
9
- described_class.parse(OpenStruct.new(code: code, body: '{}'))
10
15
  end
11
16
  end
12
17
  end
13
18
 
14
19
  [400, 401, 403, 420].each do |code|
15
20
  context "when response code is #{code}" do
21
+ before do
22
+ allow(Airbrake::Loggable.instance).to receive(:error)
23
+ end
24
+
16
25
  it "logs response message" do
17
- expect(Airbrake::Loggable.instance).to receive(:error).with(
18
- /Airbrake: foo/,
19
- )
20
26
  described_class.parse(
21
27
  OpenStruct.new(code: code, body: '{"message":"foo"}'),
22
28
  )
29
+
30
+ expect(Airbrake::Loggable.instance).to have_received(:error).with(
31
+ /Airbrake: foo/,
32
+ )
23
33
  end
24
34
  end
25
35
  end
@@ -27,11 +37,15 @@ RSpec.describe Airbrake::Response do
27
37
  context "when response code is 429" do
28
38
  let(:response) { OpenStruct.new(code: 429, body: '{"message":"rate limited"}') }
29
39
 
40
+ before do
41
+ allow(Airbrake::Loggable.instance).to receive(:error)
42
+ end
43
+
30
44
  it "logs response message" do
31
- expect(Airbrake::Loggable.instance).to receive(:error).with(
45
+ described_class.parse(response)
46
+ expect(Airbrake::Loggable.instance).to have_received(:error).with(
32
47
  /Airbrake: rate limited/,
33
48
  )
34
- described_class.parse(response)
35
49
  end
36
50
 
37
51
  it "returns an error response" do
@@ -49,11 +63,15 @@ RSpec.describe Airbrake::Response do
49
63
  context "when response code is unhandled" do
50
64
  let(:response) { OpenStruct.new(code: 500, body: 'foo') }
51
65
 
66
+ before do
67
+ allow(Airbrake::Loggable.instance).to receive(:error)
68
+ end
69
+
52
70
  it "logs response body" do
53
- expect(Airbrake::Loggable.instance).to receive(:error).with(
71
+ described_class.parse(response)
72
+ expect(Airbrake::Loggable.instance).to have_received(:error).with(
54
73
  /Airbrake: unexpected code \(500\)\. Body: foo/,
55
74
  )
56
- described_class.parse(response)
57
75
  end
58
76
 
59
77
  it "returns an error response" do
@@ -64,18 +82,22 @@ RSpec.describe Airbrake::Response do
64
82
  it "truncates body" do
65
83
  response.body *= 1000
66
84
  resp = described_class.parse(response)
67
- expect(resp).to eq('error' => ('foo' * 33) + 'fo...')
85
+ expect(resp).to eq('error' => "#{'foo' * 33}fo...")
68
86
  end
69
87
  end
70
88
 
71
89
  context "when response body can't be parsed as JSON" do
72
90
  let(:response) { OpenStruct.new(code: 201, body: 'foo') }
73
91
 
92
+ before do
93
+ allow(Airbrake::Loggable.instance).to receive(:error)
94
+ end
95
+
74
96
  it "logs response body" do
75
- expect(Airbrake::Loggable.instance).to receive(:error).with(
97
+ described_class.parse(response)
98
+ expect(Airbrake::Loggable.instance).to have_received(:error).with(
76
99
  /Airbrake: error while parsing body \(.*unexpected token.*\)\. Body: foo/,
77
100
  )
78
- described_class.parse(response)
79
101
  end
80
102
 
81
103
  it "returns an error message" do
@@ -5,19 +5,19 @@ RSpec.describe Airbrake::Stashable do
5
5
  end
6
6
 
7
7
  describe "#stash" do
8
- subject { klass.new }
8
+ subject(:instance) { klass.new }
9
9
 
10
10
  it "returns a hash" do
11
- expect(subject.stash).to be_a(Hash)
11
+ expect(instance.stash).to be_a(Hash)
12
12
  end
13
13
 
14
14
  it "returns an empty hash" do
15
- expect(subject.stash).to be_empty
15
+ expect(instance.stash).to be_empty
16
16
  end
17
17
 
18
18
  it "remembers what was put in the stash" do
19
- subject.stash[:foo] = 1
20
- expect(subject.stash[:foo]).to eq(1)
19
+ instance.stash[:foo] = 1
20
+ expect(instance.stash[:foo]).to eq(1)
21
21
  end
22
22
  end
23
23
  end
data/spec/stat_spec.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  RSpec.describe Airbrake::Stat do
2
+ subject(:stat) { described_class.new }
3
+
2
4
  describe "#to_h" do
3
5
  it "converts to a hash" do
4
- expect(subject.to_h).to eq(
6
+ expect(stat.to_h).to eq(
5
7
  'count' => 0,
6
8
  'sum' => 0.0,
7
9
  'sumsq' => 0.0,
@@ -11,19 +13,19 @@ RSpec.describe Airbrake::Stat do
11
13
  end
12
14
 
13
15
  describe "#increment_ms" do
14
- before { subject.increment_ms(1000) }
16
+ before { stat.increment_ms(1000) }
15
17
 
16
18
  its(:sum) { is_expected.to eq(1000) }
17
19
  its(:sumsq) { is_expected.to eq(1000000) }
18
20
 
19
21
  it "updates tdigest" do
20
- expect(subject.tdigest.size).to eq(1)
22
+ expect(stat.tdigest.size).to eq(1)
21
23
  end
22
24
  end
23
25
 
24
26
  describe "#inspect" do
25
27
  it "provides custom inspect output" do
26
- expect(subject.inspect).to eq(
28
+ expect(stat.inspect).to eq(
27
29
  '#<struct Airbrake::Stat count=0, sum=0.0, sumsq=0.0>',
28
30
  )
29
31
  end
@@ -31,7 +33,7 @@ RSpec.describe Airbrake::Stat do
31
33
 
32
34
  describe "#pretty_print" do
33
35
  it "is an alias of #inspect" do
34
- expect(subject.method(:pretty_print)).to eql(subject.method(:inspect))
36
+ expect(stat.method(:pretty_print)).to eql(stat.method(:inspect))
35
37
  end
36
38
  end
37
39
  end
@@ -1,4 +1,6 @@
1
1
  RSpec.describe Airbrake::SyncSender do
2
+ subject(:sync_sender) { described_class.new }
3
+
2
4
  before do
3
5
  Airbrake::Config.instance = Airbrake::Config.new(
4
6
  project_id: 1, project_key: 'banana',
@@ -14,7 +16,7 @@ RSpec.describe Airbrake::SyncSender do
14
16
  before { stub_request(:post, endpoint).to_return(body: '{}') }
15
17
 
16
18
  it "sets the Content-Type header to JSON" do
17
- subject.send({}, promise)
19
+ sync_sender.send({}, promise)
18
20
  expect(
19
21
  a_request(:post, endpoint).with(
20
22
  headers: { 'Content-Type' => 'application/json' },
@@ -23,7 +25,7 @@ RSpec.describe Airbrake::SyncSender do
23
25
  end
24
26
 
25
27
  it "sets the User-Agent header to the notifier slug" do
26
- subject.send({}, promise)
28
+ sync_sender.send({}, promise)
27
29
  expect(
28
30
  a_request(:post, endpoint).with(
29
31
  headers: {
@@ -36,7 +38,7 @@ RSpec.describe Airbrake::SyncSender do
36
38
  end
37
39
 
38
40
  it "sets the Authorization header to the project key" do
39
- subject.send({}, promise)
41
+ sync_sender.send({}, promise)
40
42
  expect(
41
43
  a_request(:post, endpoint).with(
42
44
  headers: { 'Authorization' => 'Bearer banana' },
@@ -45,19 +47,46 @@ RSpec.describe Airbrake::SyncSender do
45
47
  end
46
48
 
47
49
  it "catches exceptions raised while sending" do
50
+ # rubocop:disable RSpec/VerifiedDoubles
48
51
  https = double("foo")
49
- allow(subject).to receive(:build_https).and_return(https)
52
+ # rubocop:enable RSpec/VerifiedDoubles
53
+
54
+ # rubocop:disable RSpec/SubjectStub
55
+ allow(sync_sender).to receive(:build_https).and_return(https)
56
+ # rubocop:enable RSpec/SubjectStub
57
+
50
58
  allow(https).to receive(:request).and_raise(StandardError.new('foo'))
51
- expect(Airbrake::Loggable.instance).to receive(:error).with(
59
+
60
+ expect(sync_sender.send({}, promise)).to be_an(Airbrake::Promise)
61
+ expect(promise.value).to eq('error' => '**Airbrake: HTTP error: foo')
62
+ end
63
+
64
+ it "logs exceptions raised while sending" do
65
+ allow(Airbrake::Loggable.instance).to receive(:error)
66
+
67
+ # rubocop:disable RSpec/VerifiedDoubles
68
+ https = double("foo")
69
+ # rubocop:enable RSpec/VerifiedDoubles
70
+
71
+ # rubocop:disable RSpec/SubjectStub
72
+ allow(sync_sender).to receive(:build_https).and_return(https)
73
+ # rubocop:enable RSpec/SubjectStub
74
+
75
+ allow(https).to receive(:request).and_raise(StandardError.new('foo'))
76
+
77
+ sync_sender.send({}, promise)
78
+
79
+ expect(Airbrake::Loggable.instance).to have_received(:error).with(
52
80
  /HTTP error: foo/,
53
81
  )
54
- expect(subject.send({}, promise)).to be_an(Airbrake::Promise)
55
- expect(promise.value).to eq('error' => '**Airbrake: HTTP error: foo')
56
82
  end
57
83
 
58
84
  context "when request body is nil" do
85
+ # rubocop:disable RSpec/MultipleExpectations
59
86
  it "doesn't send data" do
60
- expect_any_instance_of(Airbrake::Truncator)
87
+ allow(Airbrake::Loggable.instance).to receive(:error)
88
+
89
+ allow_any_instance_of(Airbrake::Truncator)
61
90
  .to receive(:reduce_max_size).and_return(0)
62
91
 
63
92
  encoded = Base64.encode64("\xD3\xE6\xBC\x9D\xBA").encode!('ASCII-8BIT')
@@ -70,16 +99,18 @@ RSpec.describe Airbrake::SyncSender do
70
99
 
71
100
  notice = Airbrake::Notice.new(ex)
72
101
 
73
- expect(Airbrake::Loggable.instance).to receive(:error).with(
102
+ expect(sync_sender.send(notice, promise)).to be_an(Airbrake::Promise)
103
+ expect(promise.value)
104
+ .to match('error' => '**Airbrake: data was not sent because of missing body')
105
+
106
+ expect(Airbrake::Loggable.instance).to have_received(:error).with(
74
107
  /data was not sent/,
75
108
  )
76
- expect(Airbrake::Loggable.instance).to receive(:error).with(
109
+ expect(Airbrake::Loggable.instance).to have_received(:error).with(
77
110
  /truncation failed/,
78
111
  )
79
- expect(subject.send(notice, promise)).to be_an(Airbrake::Promise)
80
- expect(promise.value)
81
- .to match('error' => '**Airbrake: data was not sent because of missing body')
82
112
  end
113
+ # rubocop:enable RSpec/MultipleExpectations
83
114
  end
84
115
 
85
116
  context "when IP is rate limited" do
@@ -93,13 +124,14 @@ RSpec.describe Airbrake::SyncSender do
93
124
  )
94
125
  end
95
126
 
127
+ # rubocop:disable RSpec/MultipleExpectations
96
128
  it "returns error" do
97
129
  p1 = Airbrake::Promise.new
98
- subject.send({}, p1)
130
+ sync_sender.send({}, p1)
99
131
  expect(p1.value).to match('error' => '**Airbrake: IP is rate limited')
100
132
 
101
133
  p2 = Airbrake::Promise.new
102
- subject.send({}, p2)
134
+ sync_sender.send({}, p2)
103
135
  expect(p2.value).to match('error' => '**Airbrake: IP is rate limited')
104
136
 
105
137
  # Wait for X-RateLimit-Delay and then make a new request to make sure p2
@@ -107,11 +139,12 @@ RSpec.describe Airbrake::SyncSender do
107
139
  sleep 1
108
140
 
109
141
  p3 = Airbrake::Promise.new
110
- subject.send({}, p3)
142
+ sync_sender.send({}, p3)
111
143
  expect(p3.value).to match('error' => '**Airbrake: IP is rate limited')
112
144
 
113
145
  expect(a_request(:post, endpoint)).to have_been_made.twice
114
146
  end
147
+ # rubocop:enable RSpec/MultipleExpectations
115
148
  end
116
149
 
117
150
  context "when the provided method is :put" do