airbrake-ruby 4.15.0 → 6.0.2

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 (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 +71 -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 +4 -1
  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 +5 -3
  50. data/spec/code_hunk_spec.rb +26 -17
  51. data/spec/config/processor_spec.rb +151 -0
  52. data/spec/config/validator_spec.rb +23 -3
  53. data/spec/config_spec.rb +40 -52
  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 +9 -9
  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 +65 -56
  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 +66 -50
@@ -0,0 +1,348 @@
1
+ RSpec.describe Airbrake::RemoteSettings::SettingsData do
2
+ let(:project_id) { 123 }
3
+
4
+ describe "#merge!" do
5
+ it "returns self" do
6
+ settings_data = described_class.new(project_id, {})
7
+ expect(settings_data.merge!({})).to eql(settings_data)
8
+ end
9
+
10
+ it "merges the given hash with the data" do
11
+ settings_data = described_class.new(project_id, {})
12
+ settings_data.merge!('poll_sec' => 123, 'config_route' => 'abc')
13
+
14
+ expect(settings_data.interval).to eq(123)
15
+ expect(settings_data.config_route(''))
16
+ .to eq('/abc')
17
+ end
18
+ end
19
+
20
+ describe "#interval" do
21
+ context "when given data has zero interval" do
22
+ let(:data) do
23
+ { 'poll_sec' => 0 }
24
+ end
25
+
26
+ it "returns the default interval" do
27
+ expect(described_class.new(project_id, data).interval).to eq(600)
28
+ end
29
+ end
30
+
31
+ context "when given data has negative interval" do
32
+ let(:data) do
33
+ { 'poll_sec' => -1 }
34
+ end
35
+
36
+ it "returns the default interval" do
37
+ expect(described_class.new(project_id, data).interval).to eq(600)
38
+ end
39
+ end
40
+
41
+ context "when given data has nil interval" do
42
+ let(:data) do
43
+ { 'poll_sec' => nil }
44
+ end
45
+
46
+ it "returns the default interval" do
47
+ expect(described_class.new(project_id, data).interval).to eq(600)
48
+ end
49
+ end
50
+
51
+ context "when given data has a positive interval" do
52
+ let(:data) do
53
+ { 'poll_sec' => 123 }
54
+ end
55
+
56
+ it "returns the interval from data" do
57
+ expect(described_class.new(project_id, data).interval).to eq(123)
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "#config_route" do
63
+ let(:host) { 'http://example.com/' }
64
+
65
+ context "when remote config specifies a config route" do
66
+ let(:data) do
67
+ { 'config_route' => '123/cfg/321/cfg.json' }
68
+ end
69
+
70
+ it "returns the config route with the provided location" do
71
+ expect(described_class.new(project_id, data).config_route(host)).to eq(
72
+ 'http://example.com/123/cfg/321/cfg.json',
73
+ )
74
+ end
75
+ end
76
+
77
+ context "when remote config DOES NOT specify a config route" do
78
+ it "returns the config route with the default location" do
79
+ expect(described_class.new(project_id, {}).config_route(host)).to eq(
80
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
81
+ )
82
+ end
83
+ end
84
+
85
+ context "when a config route is specified but is set to nil" do
86
+ let(:data) do
87
+ { 'config_route' => nil }
88
+ end
89
+
90
+ it "returns the config route with the default location" do
91
+ expect(described_class.new(project_id, data).config_route(host)).to eq(
92
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
93
+ )
94
+ end
95
+ end
96
+
97
+ context "when a config route is specified but is set to an empty string" do
98
+ let(:data) do
99
+ { 'config_route' => '' }
100
+ end
101
+
102
+ it "returns the route with the default instead" do
103
+ expect(described_class.new(project_id, data).config_route(host)).to eq(
104
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
105
+ )
106
+ end
107
+ end
108
+ end
109
+
110
+ describe "#error_notifications?" do
111
+ context "when the 'errors' setting is present" do
112
+ context "and when it is enabled" do
113
+ let(:data) do
114
+ {
115
+ 'settings' => [
116
+ {
117
+ 'name' => 'errors',
118
+ 'enabled' => true,
119
+ },
120
+ ],
121
+ }
122
+ end
123
+
124
+ it "returns true" do
125
+ expect(described_class.new(project_id, data).error_notifications?)
126
+ .to eq(true)
127
+ end
128
+ end
129
+
130
+ context "and when it is disabled" do
131
+ let(:data) do
132
+ {
133
+ 'settings' => [
134
+ {
135
+ 'name' => 'errors',
136
+ 'enabled' => false,
137
+ },
138
+ ],
139
+ }
140
+ end
141
+
142
+ it "returns false" do
143
+ expect(described_class.new(project_id, data).error_notifications?)
144
+ .to eq(false)
145
+ end
146
+ end
147
+ end
148
+
149
+ context "when the 'errors' setting is missing" do
150
+ let(:data) do
151
+ { 'settings' => [] }
152
+ end
153
+
154
+ it "returns true" do
155
+ expect(described_class.new(project_id, data).error_notifications?)
156
+ .to eq(true)
157
+ end
158
+ end
159
+ end
160
+
161
+ describe "#performance_stats?" do
162
+ context "when the 'apm' setting is present" do
163
+ context "and when it is enabled" do
164
+ let(:data) do
165
+ {
166
+ 'settings' => [
167
+ {
168
+ 'name' => 'apm',
169
+ 'enabled' => true,
170
+ },
171
+ ],
172
+ }
173
+ end
174
+
175
+ it "returns true" do
176
+ expect(described_class.new(project_id, data).performance_stats?)
177
+ .to eq(true)
178
+ end
179
+ end
180
+
181
+ context "and when it is disabled" do
182
+ let(:data) do
183
+ {
184
+ 'settings' => [
185
+ {
186
+ 'name' => 'apm',
187
+ 'enabled' => false,
188
+ },
189
+ ],
190
+ }
191
+ end
192
+
193
+ it "returns false" do
194
+ expect(described_class.new(project_id, data).performance_stats?)
195
+ .to eq(false)
196
+ end
197
+ end
198
+ end
199
+
200
+ context "when the 'apm' setting is missing" do
201
+ let(:data) do
202
+ { 'settings' => [] }
203
+ end
204
+
205
+ it "returns true" do
206
+ expect(described_class.new(project_id, data).performance_stats?)
207
+ .to eq(true)
208
+ end
209
+ end
210
+ end
211
+
212
+ describe "#error_host" do
213
+ context "when the 'errors' setting is present" do
214
+ context "and when 'endpoint' is specified" do
215
+ let(:endpoint) { 'https://api.example.com/' }
216
+
217
+ let(:data) do
218
+ {
219
+ 'settings' => [
220
+ {
221
+ 'name' => 'errors',
222
+ 'enabled' => true,
223
+ 'endpoint' => endpoint,
224
+ },
225
+ ],
226
+ }
227
+ end
228
+
229
+ it "returns the endpoint" do
230
+ expect(described_class.new(project_id, data).error_host).to eq(endpoint)
231
+ end
232
+ end
233
+
234
+ context "and when an endpoint is NOT specified" do
235
+ let(:data) do
236
+ {
237
+ 'settings' => [
238
+ {
239
+ 'name' => 'errors',
240
+ 'enabled' => true,
241
+ },
242
+ ],
243
+ }
244
+ end
245
+
246
+ it "returns nil" do
247
+ expect(described_class.new(project_id, data).error_host).to be_nil
248
+ end
249
+ end
250
+ end
251
+
252
+ context "when the 'errors' setting is missing" do
253
+ let(:data) do
254
+ { 'settings' => [] }
255
+ end
256
+
257
+ it "returns nil" do
258
+ expect(described_class.new(project_id, data).error_host).to be_nil
259
+ end
260
+ end
261
+ end
262
+
263
+ describe "#apm_host" do
264
+ context "when the 'apm' setting is present" do
265
+ context "and when 'endpoint' is specified" do
266
+ let(:endpoint) { 'https://api.example.com/' }
267
+
268
+ let(:data) do
269
+ {
270
+ 'settings' => [
271
+ {
272
+ 'name' => 'apm',
273
+ 'enabled' => true,
274
+ 'endpoint' => endpoint,
275
+ },
276
+ ],
277
+ }
278
+ end
279
+
280
+ it "returns the endpoint" do
281
+ expect(described_class.new(project_id, data).apm_host).to eq(endpoint)
282
+ end
283
+ end
284
+
285
+ context "and when an endpoint is NOT specified" do
286
+ let(:data) do
287
+ {
288
+ 'settings' => [
289
+ {
290
+ 'name' => 'apm',
291
+ 'enabled' => true,
292
+ },
293
+ ],
294
+ }
295
+ end
296
+
297
+ it "returns nil" do
298
+ expect(described_class.new(project_id, data).apm_host).to be_nil
299
+ end
300
+ end
301
+ end
302
+
303
+ context "when the 'apm' setting is missing" do
304
+ let(:data) do
305
+ { 'settings' => [] }
306
+ end
307
+
308
+ it "returns nil" do
309
+ expect(described_class.new(project_id, data).apm_host).to be_nil
310
+ end
311
+ end
312
+ end
313
+
314
+ describe "#to_h" do
315
+ subject(:settings_data) { described_class.new(project_id, data) }
316
+
317
+ let(:data) do
318
+ {
319
+ 'poll_sec' => 123,
320
+ 'settings' => [
321
+ {
322
+ 'name' => 'apm',
323
+ 'enabled' => false,
324
+ },
325
+ ],
326
+ }
327
+ end
328
+
329
+ it "returns a hash representation of settings" do
330
+ expect(described_class.new(project_id, data).to_h).to eq(data)
331
+ end
332
+
333
+ it "doesn't allow mutation of the original data object" do
334
+ hash = settings_data.to_h
335
+ hash['poll_sec'] = 0
336
+
337
+ expect(settings_data.to_h).to eq(
338
+ 'poll_sec' => 123,
339
+ 'settings' => [
340
+ {
341
+ 'name' => 'apm',
342
+ 'enabled' => false,
343
+ },
344
+ ],
345
+ )
346
+ end
347
+ end
348
+ end
@@ -0,0 +1,201 @@
1
+ RSpec.describe Airbrake::RemoteSettings do
2
+ let(:project_id) { 123 }
3
+ let(:host) { 'https://v1-production-notifier-configs.s3.amazonaws.com' }
4
+
5
+ let(:endpoint) do
6
+ "#{host}/2020-06-18/config/#{project_id}/config.json"
7
+ end
8
+
9
+ let(:body) do
10
+ {
11
+ 'poll_sec' => 1,
12
+ 'settings' => [
13
+ {
14
+ 'name' => 'apm',
15
+ 'enabled' => false,
16
+ },
17
+ {
18
+ 'name' => 'errors',
19
+ 'enabled' => true,
20
+ },
21
+ ],
22
+ }
23
+ end
24
+
25
+ let!(:stub) do
26
+ stub_request(:get, Regexp.new(endpoint))
27
+ .to_return(status: 200, body: body.to_json)
28
+ end
29
+
30
+ describe ".poll" do
31
+ describe "config loading" do
32
+ let(:settings_data) { described_class::SettingsData.new(project_id, body) }
33
+
34
+ before do
35
+ allow(described_class::SettingsData).to receive(:new).and_return(settings_data)
36
+ end
37
+
38
+ it "yields the config to the block twice" do
39
+ block = proc {}
40
+ allow(block).to receive(:call)
41
+
42
+ remote_settings = described_class.poll(project_id, host, &block)
43
+ sleep(0.2)
44
+ remote_settings.stop_polling
45
+
46
+ expect(stub).to have_been_requested.once
47
+ expect(block).to have_received(:call).twice
48
+ end
49
+ end
50
+
51
+ context "when no errors are raised" do
52
+ it "makes a request to AWS S3" do
53
+ remote_settings = described_class.poll(project_id, host) { anything }
54
+ sleep(0.1)
55
+ remote_settings.stop_polling
56
+
57
+ expect(stub).to have_been_requested.at_least_once
58
+ end
59
+
60
+ it "sends params about the environment with the request" do
61
+ remote_settings = described_class.poll(project_id, host) { anything }
62
+ sleep(0.1)
63
+ remote_settings.stop_polling
64
+
65
+ stub_with_query_params = stub.with(
66
+ query: URI.decode_www_form(described_class::QUERY_PARAMS).to_h,
67
+ )
68
+ expect(stub_with_query_params).to have_been_requested.at_least_once
69
+ end
70
+
71
+ # rubocop:disable RSpec/MultipleExpectations
72
+ it "fetches remote settings" do
73
+ settings = nil
74
+ remote_settings = described_class.poll(project_id, host) do |data|
75
+ settings = data
76
+ end
77
+ sleep(0.1)
78
+ remote_settings.stop_polling
79
+
80
+ expect(settings.error_notifications?).to eq(true)
81
+ expect(settings.performance_stats?).to eq(false)
82
+ expect(settings.interval).to eq(1)
83
+ end
84
+ # rubocop:enable RSpec/MultipleExpectations
85
+ end
86
+
87
+ context "when an error is raised while making a HTTP request" do
88
+ let(:https) { instance_double(Net::HTTP) }
89
+
90
+ before do
91
+ allow(Net::HTTP).to receive(:new).and_return(https)
92
+ allow(https).to receive(:use_ssl=).with(true)
93
+ allow(https).to receive(:request).and_raise(StandardError)
94
+ end
95
+
96
+ it "doesn't fetch remote settings" do
97
+ settings = nil
98
+ remote_settings = described_class.poll(project_id, host) do |data|
99
+ settings = data
100
+ end
101
+ sleep(0.1)
102
+ remote_settings.stop_polling
103
+
104
+ expect(stub).not_to have_been_requested
105
+ expect(settings.interval).to eq(600)
106
+ end
107
+ end
108
+
109
+ context "when an error is raised while parsing returned JSON" do
110
+ before do
111
+ allow(JSON).to receive(:parse).and_raise(JSON::ParserError)
112
+ end
113
+
114
+ it "doesn't update settings data" do
115
+ settings = nil
116
+ remote_settings = described_class.poll(project_id, host) do |data|
117
+ settings = data
118
+ end
119
+ sleep(0.1)
120
+ remote_settings.stop_polling
121
+
122
+ expect(stub).to have_been_requested.once
123
+ expect(settings.interval).to eq(600)
124
+ end
125
+ end
126
+
127
+ context "when API returns a non-200 response" do
128
+ let!(:stub) do
129
+ stub_request(:get, Regexp.new(endpoint))
130
+ .to_return(status: 201, body: body.to_json)
131
+ end
132
+
133
+ it "doesn't update settings data" do
134
+ settings = nil
135
+ remote_settings = described_class.poll(project_id, host) do |data|
136
+ settings = data
137
+ end
138
+ sleep(0.1)
139
+ remote_settings.stop_polling
140
+
141
+ expect(stub).to have_been_requested.once
142
+ expect(settings.interval).to eq(600)
143
+ end
144
+
145
+ it "logs error" do
146
+ allow(Airbrake::Loggable.instance).to receive(:error)
147
+
148
+ remote_settings = described_class.poll(project_id, host) { anything }
149
+ sleep(0.1)
150
+ remote_settings.stop_polling
151
+
152
+ expect(Airbrake::Loggable.instance).to have_received(:error).with(body.to_json)
153
+ end
154
+ end
155
+
156
+ context "when API returns a 200 response" do
157
+ let!(:stub) do
158
+ stub_request(:get, Regexp.new(endpoint))
159
+ .to_return(status: 200, body: body.to_json)
160
+ end
161
+
162
+ it "doesn't log errors" do
163
+ allow(Airbrake::Loggable.instance).to receive(:error)
164
+
165
+ remote_settings = described_class.poll(project_id, host) { anything }
166
+ sleep(0.1)
167
+ remote_settings.stop_polling
168
+
169
+ expect(Airbrake::Loggable.instance).not_to have_received(:error)
170
+ expect(stub).to have_been_requested.once
171
+ end
172
+ end
173
+
174
+ # rubocop:disable RSpec/MultipleMemoizedHelpers
175
+ context "when a config route is specified in the returned data" do
176
+ let(:new_config_route) do
177
+ '213/config/111/config.json'
178
+ end
179
+
180
+ let(:body) do
181
+ { 'config_route' => new_config_route, 'poll_sec' => 0.1 }
182
+ end
183
+
184
+ let!(:new_stub) do
185
+ stub_request(:get, Regexp.new(new_config_route))
186
+ .to_return(status: 200, body: body.to_json)
187
+ end
188
+
189
+ it "makes the next request to the specified config route" do
190
+ remote_settings = described_class.poll(project_id, host) { anything }
191
+ sleep(0.2)
192
+
193
+ remote_settings.stop_polling
194
+
195
+ expect(stub).to have_been_requested.once
196
+ expect(new_stub).to have_been_requested.once
197
+ end
198
+ end
199
+ # rubocop:enable RSpec/MultipleMemoizedHelpers
200
+ end
201
+ end
data/spec/request_spec.rb CHANGED
@@ -1,21 +1,9 @@
1
1
  RSpec.describe Airbrake::Request do
2
2
  describe "#stash" do
3
3
  subject do
4
- described_class.new(
5
- method: 'GET', route: '/', status_code: 200, start_time: Time.now,
6
- )
4
+ described_class.new(method: 'GET', route: '/', status_code: 200)
7
5
  end
8
6
 
9
7
  it { is_expected.to respond_to(:stash) }
10
8
  end
11
-
12
- describe "#end_time" do
13
- it "is always equal to start_time + 1 second by default" do
14
- time = Time.now
15
- request = described_class.new(
16
- method: 'GET', route: '/', status_code: 200, start_time: time,
17
- )
18
- expect(request.end_time).to eq(time + 1)
19
- end
20
- end
21
9
  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
data/spec/spec_helper.rb CHANGED
@@ -33,7 +33,7 @@ class AirbrakeTestError < RuntimeError
33
33
 
34
34
  def initialize(*)
35
35
  super
36
- # rubocop:disable Metrics/LineLength
36
+ # rubocop:disable Layout/LineLength
37
37
  @backtrace = [
38
38
  "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb:23:in `<top (required)>'",
39
39
  "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
@@ -49,7 +49,7 @@ class AirbrakeTestError < RuntimeError
49
49
  "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:41:in `invoke'",
50
50
  "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/exe/rspec:4:in `<main>'",
51
51
  ]
52
- # rubocop:enable Metrics/LineLength
52
+ # rubocop:enable Layout/LineLength
53
53
  end
54
54
 
55
55
  # rubocop:disable Naming/AccessorMethodName
@@ -66,7 +66,7 @@ end
66
66
  class JavaAirbrakeTestError < AirbrakeTestError
67
67
  def initialize(*)
68
68
  super
69
- # rubocop:disable Metrics/LineLength
69
+ # rubocop:disable Layout/LineLength
70
70
  @backtrace = [
71
71
  "org.jruby.java.invokers.InstanceMethodInvoker.call(InstanceMethodInvoker.java:26)",
72
72
  "org.jruby.ir.interpreter.Interpreter.INTERPRET_EVAL(Interpreter.java:126)",
@@ -80,7 +80,7 @@ class JavaAirbrakeTestError < AirbrakeTestError
80
80
  "org.jruby.Main.run(Main.java:225)",
81
81
  "org.jruby.Main.main(Main.java:197)",
82
82
  ]
83
- # rubocop:enable Metrics/LineLength
83
+ # rubocop:enable Layout/LineLength
84
84
  end
85
85
 
86
86
  def is_a?(*)