airbrake-ruby 4.13.3-java → 5.0.0.rc.1-java

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +23 -32
  3. data/lib/airbrake-ruby/async_sender.rb +1 -1
  4. data/lib/airbrake-ruby/config.rb +65 -13
  5. data/lib/airbrake-ruby/config/processor.rb +80 -0
  6. data/lib/airbrake-ruby/config/validator.rb +4 -0
  7. data/lib/airbrake-ruby/filter_chain.rb +15 -1
  8. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +2 -1
  9. data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
  10. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  11. data/lib/airbrake-ruby/filters/keys_filter.rb +5 -5
  12. data/lib/airbrake-ruby/notice_notifier.rb +6 -0
  13. data/lib/airbrake-ruby/performance_notifier.rb +1 -1
  14. data/lib/airbrake-ruby/remote_settings.rb +128 -0
  15. data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
  16. data/lib/airbrake-ruby/sync_sender.rb +1 -1
  17. data/lib/airbrake-ruby/thread_pool.rb +1 -0
  18. data/lib/airbrake-ruby/version.rb +1 -1
  19. data/spec/airbrake_spec.rb +71 -36
  20. data/spec/config/processor_spec.rb +223 -0
  21. data/spec/config/validator_spec.rb +18 -1
  22. data/spec/config_spec.rb +38 -6
  23. data/spec/filter_chain_spec.rb +27 -0
  24. data/spec/filters/git_last_checkout_filter_spec.rb +20 -3
  25. data/spec/filters/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +10 -10
  26. data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +10 -10
  27. data/spec/filters/sql_filter_spec.rb +3 -3
  28. data/spec/notice_notifier/options_spec.rb +4 -4
  29. data/spec/performance_notifier_spec.rb +2 -2
  30. data/spec/remote_settings/settings_data_spec.rb +327 -0
  31. data/spec/remote_settings_spec.rb +212 -0
  32. data/spec/sync_sender_spec.rb +3 -1
  33. data/spec/thread_pool_spec.rb +25 -5
  34. metadata +20 -12
@@ -1,4 +1,4 @@
1
- RSpec.describe Airbrake::Filters::KeysBlacklist do
1
+ RSpec.describe Airbrake::Filters::KeysBlocklist do
2
2
  subject { described_class.new(patterns) }
3
3
 
4
4
  let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
@@ -91,10 +91,10 @@ RSpec.describe Airbrake::Filters::KeysBlacklist do
91
91
 
92
92
  it "logs an error" do
93
93
  expect(Airbrake::Loggable.instance).to receive(:error).with(
94
- /KeysBlacklist is invalid.+patterns: \[#<Object:.+>\]/,
94
+ /KeysBlocklist is invalid.+patterns: \[#<Object:.+>\]/,
95
95
  )
96
- keys_blacklist = described_class.new(patterns)
97
- keys_blacklist.call(notice)
96
+ keys_blocklist = described_class.new(patterns)
97
+ keys_blocklist.call(notice)
98
98
  end
99
99
  end
100
100
 
@@ -104,10 +104,10 @@ RSpec.describe Airbrake::Filters::KeysBlacklist do
104
104
  context "and when the filter is called once" do
105
105
  it "logs an error" do
106
106
  expect(Airbrake::Loggable.instance).to receive(:error).with(
107
- /KeysBlacklist is invalid.+patterns: \[#<Proc:.+>\]/,
107
+ /KeysBlocklist is invalid.+patterns: \[#<Proc:.+>\]/,
108
108
  )
109
- keys_blacklist = described_class.new(patterns)
110
- keys_blacklist.call(notice)
109
+ keys_blocklist = described_class.new(patterns)
110
+ keys_blocklist.call(notice)
111
111
  end
112
112
  end
113
113
 
@@ -133,10 +133,10 @@ RSpec.describe Airbrake::Filters::KeysBlacklist do
133
133
 
134
134
  it "logs an error" do
135
135
  expect(Airbrake::Loggable.instance).to receive(:error).with(
136
- /KeysBlacklist is invalid.+patterns: \[#<Object:.+>\]/,
136
+ /KeysBlocklist is invalid.+patterns: \[#<Object:.+>\]/,
137
137
  )
138
- keys_blacklist = described_class.new(patterns)
139
- keys_blacklist.call(notice)
138
+ keys_blocklist = described_class.new(patterns)
139
+ keys_blocklist.call(notice)
140
140
  end
141
141
  end
142
142
 
@@ -10,7 +10,7 @@ RSpec.describe Airbrake::Filters::SqlFilter do
10
10
  end
11
11
  end
12
12
 
13
- shared_examples "query blacklisting" do |query, opts|
13
+ shared_examples "query blocklisting" do |query, opts|
14
14
  it "ignores '#{query}'" do
15
15
  filter = described_class.new('postgres')
16
16
  q = Airbrake::Query.new(query: query, method: 'GET', route: '/', timing: 1)
@@ -263,12 +263,12 @@ RSpec.describe Airbrake::Filters::SqlFilter do
263
263
 
264
264
  'SELECT t.oid, t.typname FROM pg_type as t WHERE t.typname IN (?)',
265
265
  ].each do |query|
266
- include_examples 'query blacklisting', query, should_ignore: true
266
+ include_examples 'query blocklisting', query, should_ignore: true
267
267
  end
268
268
 
269
269
  [
270
270
  'UPDATE "users" SET "last_sign_in_at" = ? WHERE "users"."id" = ?',
271
271
  ].each do |query|
272
- include_examples 'query blacklisting', query, should_ignore: false
272
+ include_examples 'query blocklisting', query, should_ignore: false
273
273
  end
274
274
  end
@@ -234,14 +234,14 @@ RSpec.describe Airbrake::NoticeNotifier do
234
234
  end
235
235
  end
236
236
 
237
- describe ":blacklist_keys" do
237
+ describe ":blocklist_keys" do
238
238
  # Fixes https://github.com/airbrake/airbrake-ruby/issues/276
239
- context "when specified along with :whitelist_keys" do
239
+ context "when specified along with :allowlist_keys" do
240
240
  context "and when context payload is present" do
241
241
  before do
242
242
  Airbrake::Config.instance.merge(
243
- blacklist_keys: %i[password password_confirmation],
244
- whitelist_keys: [:email, /user/i, 'account_id'],
243
+ blocklist_keys: %i[password password_confirmation],
244
+ allowlist_keys: [:email, /user/i, 'account_id'],
245
245
  )
246
246
  end
247
247
 
@@ -389,7 +389,7 @@ RSpec.describe Airbrake::PerformanceNotifier do
389
389
  subject.close
390
390
 
391
391
  expect(promise).to be_an(Airbrake::Promise)
392
- expect(promise.value).to eq('' => nil)
392
+ expect(promise.value).to eq('' => '')
393
393
  end
394
394
 
395
395
  it "checks performance stat configuration" do
@@ -601,7 +601,7 @@ RSpec.describe Airbrake::PerformanceNotifier do
601
601
  body: %r|\A{"queries":\[{"method":"POST","route":"/foo"|,
602
602
  ),
603
603
  ).to have_been_made
604
- expect(retval).to eq('' => nil)
604
+ expect(retval).to eq('' => '')
605
605
  end
606
606
  end
607
607
 
@@ -0,0 +1,327 @@
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
+ # rubocop:disable Performance/RedundantMerge
13
+ settings_data.merge!('poll_sec' => 123, 'config_route' => 'abc')
14
+ # rubocop:enable Performance/RedundantMerge
15
+
16
+ expect(settings_data.interval).to eq(123)
17
+ expect(settings_data.config_route).to eq('abc')
18
+ end
19
+ end
20
+
21
+ describe "#interval" do
22
+ context "when given data has zero interval" do
23
+ let(:data) do
24
+ { 'poll_sec' => 0 }
25
+ end
26
+
27
+ it "returns the default interval" do
28
+ expect(described_class.new(project_id, data).interval).to eq(600)
29
+ end
30
+ end
31
+
32
+ context "when given data has negative interval" do
33
+ let(:data) do
34
+ { 'poll_sec' => -1 }
35
+ end
36
+
37
+ it "returns the default interval" do
38
+ expect(described_class.new(project_id, data).interval).to eq(600)
39
+ end
40
+ end
41
+
42
+ context "when given data has nil interval" do
43
+ let(:data) do
44
+ { 'poll_sec' => nil }
45
+ end
46
+
47
+ it "returns the default interval" do
48
+ expect(described_class.new(project_id, data).interval).to eq(600)
49
+ end
50
+ end
51
+
52
+ context "when given data has a positive interval" do
53
+ let(:data) do
54
+ { 'poll_sec' => 123 }
55
+ end
56
+
57
+ it "returns the interval from data" do
58
+ expect(described_class.new(project_id, data).interval).to eq(123)
59
+ end
60
+ end
61
+ end
62
+
63
+ describe "#config_route" do
64
+ context "when given a pathname" do
65
+ let(:data) do
66
+ { 'config_route' => 'http://example.com' }
67
+ end
68
+
69
+ it "returns the given pathname" do
70
+ expect(described_class.new(project_id, data).config_route)
71
+ .to eq('http://example.com')
72
+ end
73
+ end
74
+
75
+ context "when the pathname is nil" do
76
+ let(:data) do
77
+ { 'config_route' => nil }
78
+ end
79
+
80
+ it "returns the default pathname" do
81
+ expect(described_class.new(project_id, data).config_route).to eq(
82
+ 'https://staging-notifier-configs.s3.amazonaws.com/' \
83
+ "2020-06-18/config/#{project_id}/config.json",
84
+ )
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "#error_notifications?" do
90
+ context "when the 'errors' setting is present" do
91
+ context "and when it is enabled" do
92
+ let(:data) do
93
+ {
94
+ 'settings' => [
95
+ {
96
+ 'name' => 'errors',
97
+ 'enabled' => true,
98
+ },
99
+ ],
100
+ }
101
+ end
102
+
103
+ it "returns true" do
104
+ expect(described_class.new(project_id, data).error_notifications?)
105
+ .to eq(true)
106
+ end
107
+ end
108
+
109
+ context "and when it is disabled" do
110
+ let(:data) do
111
+ {
112
+ 'settings' => [
113
+ {
114
+ 'name' => 'errors',
115
+ 'enabled' => false,
116
+ },
117
+ ],
118
+ }
119
+ end
120
+
121
+ it "returns false" do
122
+ expect(described_class.new(project_id, data).error_notifications?)
123
+ .to eq(false)
124
+ end
125
+ end
126
+ end
127
+
128
+ context "when the 'errors' setting is missing" do
129
+ let(:data) do
130
+ { 'settings' => [] }
131
+ end
132
+
133
+ it "returns true" do
134
+ expect(described_class.new(project_id, data).error_notifications?)
135
+ .to eq(true)
136
+ end
137
+ end
138
+ end
139
+
140
+ describe "#performance_stats?" do
141
+ context "when the 'apm' setting is present" do
142
+ context "and when it is enabled" do
143
+ let(:data) do
144
+ {
145
+ 'settings' => [
146
+ {
147
+ 'name' => 'apm',
148
+ 'enabled' => true,
149
+ },
150
+ ],
151
+ }
152
+ end
153
+
154
+ it "returns true" do
155
+ expect(described_class.new(project_id, data).performance_stats?)
156
+ .to eq(true)
157
+ end
158
+ end
159
+
160
+ context "and when it is disabled" do
161
+ let(:data) do
162
+ {
163
+ 'settings' => [
164
+ {
165
+ 'name' => 'apm',
166
+ 'enabled' => false,
167
+ },
168
+ ],
169
+ }
170
+ end
171
+
172
+ it "returns false" do
173
+ expect(described_class.new(project_id, data).performance_stats?)
174
+ .to eq(false)
175
+ end
176
+ end
177
+ end
178
+
179
+ context "when the 'apm' setting is missing" do
180
+ let(:data) do
181
+ { 'settings' => [] }
182
+ end
183
+
184
+ it "returns true" do
185
+ expect(described_class.new(project_id, data).performance_stats?)
186
+ .to eq(true)
187
+ end
188
+ end
189
+ end
190
+
191
+ describe "#error_host" do
192
+ context "when the 'errors' setting is present" do
193
+ context "and when 'endpoint' is specified" do
194
+ let(:endpoint) { 'https://api.example.com/' }
195
+
196
+ let(:data) do
197
+ {
198
+ 'settings' => [
199
+ {
200
+ 'name' => 'errors',
201
+ 'enabled' => true,
202
+ 'endpoint' => endpoint,
203
+ },
204
+ ],
205
+ }
206
+ end
207
+
208
+ it "returns the endpoint" do
209
+ expect(described_class.new(project_id, data).error_host).to eq(endpoint)
210
+ end
211
+ end
212
+
213
+ context "and when an endpoint is NOT specified" do
214
+ let(:data) do
215
+ {
216
+ 'settings' => [
217
+ {
218
+ 'name' => 'errors',
219
+ 'enabled' => true,
220
+ },
221
+ ],
222
+ }
223
+ end
224
+
225
+ it "returns nil" do
226
+ expect(described_class.new(project_id, data).error_host).to be_nil
227
+ end
228
+ end
229
+ end
230
+
231
+ context "when the 'errors' setting is missing" do
232
+ let(:data) do
233
+ { 'settings' => [] }
234
+ end
235
+
236
+ it "returns nil" do
237
+ expect(described_class.new(project_id, data).error_host).to be_nil
238
+ end
239
+ end
240
+ end
241
+
242
+ describe "#apm_host" do
243
+ context "when the 'apm' setting is present" do
244
+ context "and when 'endpoint' is specified" do
245
+ let(:endpoint) { 'https://api.example.com/' }
246
+
247
+ let(:data) do
248
+ {
249
+ 'settings' => [
250
+ {
251
+ 'name' => 'apm',
252
+ 'enabled' => true,
253
+ 'endpoint' => endpoint,
254
+ },
255
+ ],
256
+ }
257
+ end
258
+
259
+ it "returns the endpoint" do
260
+ expect(described_class.new(project_id, data).apm_host).to eq(endpoint)
261
+ end
262
+ end
263
+
264
+ context "and when an endpoint is NOT specified" do
265
+ let(:data) do
266
+ {
267
+ 'settings' => [
268
+ {
269
+ 'name' => 'apm',
270
+ 'enabled' => true,
271
+ },
272
+ ],
273
+ }
274
+ end
275
+
276
+ it "returns nil" do
277
+ expect(described_class.new(project_id, data).apm_host).to be_nil
278
+ end
279
+ end
280
+ end
281
+
282
+ context "when the 'apm' setting is missing" do
283
+ let(:data) do
284
+ { 'settings' => [] }
285
+ end
286
+
287
+ it "returns nil" do
288
+ expect(described_class.new(project_id, data).apm_host).to be_nil
289
+ end
290
+ end
291
+ end
292
+
293
+ describe "#to_h" do
294
+ let(:data) do
295
+ {
296
+ 'poll_sec' => 123,
297
+ 'settings' => [
298
+ {
299
+ 'name' => 'apm',
300
+ 'enabled' => false,
301
+ },
302
+ ],
303
+ }
304
+ end
305
+
306
+ subject { described_class.new(project_id, data) }
307
+
308
+ it "returns a hash representation of settings" do
309
+ expect(described_class.new(project_id, data).to_h).to eq(data)
310
+ end
311
+
312
+ it "doesn't allow mutation of the original data object" do
313
+ hash = subject.to_h
314
+ hash['poll_sec'] = 0
315
+
316
+ expect(subject.to_h).to eq(
317
+ 'poll_sec' => 123,
318
+ 'settings' => [
319
+ {
320
+ 'name' => 'apm',
321
+ 'enabled' => false,
322
+ },
323
+ ],
324
+ )
325
+ end
326
+ end
327
+ end
@@ -0,0 +1,212 @@
1
+ RSpec.describe Airbrake::RemoteSettings do
2
+ let(:project_id) { 123 }
3
+
4
+ let(:endpoint) do
5
+ "https://staging-notifier-configs.s3.amazonaws.com/2020-06-18/config/" \
6
+ "#{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(:config_path) { described_class::CONFIG_DUMP_PATH }
26
+ let(:config_dir) { File.dirname(config_path) }
27
+
28
+ before do
29
+ stub_request(:get, endpoint).to_return(status: 200, body: body.to_json)
30
+
31
+ # Do not create config dumps on disk.
32
+ allow(Dir).to receive(:mkdir).with(config_dir)
33
+ allow(File).to receive(:write).with(config_path, anything)
34
+ end
35
+
36
+ describe ".poll" do
37
+ describe "config loading" do
38
+ let(:settings_data) { described_class::SettingsData.new(project_id, body) }
39
+
40
+ before do
41
+ allow(File).to receive(:exist?).with(config_path).and_return(true)
42
+ allow(File).to receive(:read).with(config_path).and_return(body.to_json)
43
+
44
+ allow(described_class::SettingsData).to receive(:new).and_return(settings_data)
45
+ end
46
+
47
+ it "loads the config from disk" do
48
+ expect(File).to receive(:read).with(config_path)
49
+ expect(settings_data).to receive(:merge!).with(body).twice
50
+
51
+ remote_settings = described_class.poll(project_id) {}
52
+ sleep(0.2)
53
+ remote_settings.stop_polling
54
+
55
+ expect(a_request(:get, endpoint)).to have_been_made.once
56
+ end
57
+
58
+ it "yields the config to the block twice" do
59
+ block = proc {}
60
+ expect(block).to receive(:call).twice
61
+
62
+ remote_settings = described_class.poll(project_id, &block)
63
+ sleep(0.2)
64
+ remote_settings.stop_polling
65
+
66
+ expect(a_request(:get, endpoint)).to have_been_made.once
67
+ end
68
+
69
+ context "when config loading fails" do
70
+ it "logs an error" do
71
+ expect(File).to receive(:read).and_raise(StandardError)
72
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
73
+ '**Airbrake: config loading failed: StandardError',
74
+ )
75
+
76
+ remote_settings = described_class.poll(project_id) {}
77
+ sleep(0.2)
78
+ remote_settings.stop_polling
79
+
80
+ expect(a_request(:get, endpoint)).to have_been_made.once
81
+ end
82
+ end
83
+ end
84
+
85
+ context "when no errors are raised" do
86
+ it "makes a request to AWS S3" do
87
+ remote_settings = described_class.poll(project_id) {}
88
+ sleep(0.1)
89
+ remote_settings.stop_polling
90
+
91
+ expect(a_request(:get, endpoint)).to have_been_made.at_least_once
92
+ end
93
+
94
+ it "fetches remote settings" do
95
+ settings = nil
96
+ remote_settings = described_class.poll(project_id) do |data|
97
+ settings = data
98
+ end
99
+ sleep(0.1)
100
+ remote_settings.stop_polling
101
+
102
+ expect(settings.error_notifications?).to eq(true)
103
+ expect(settings.performance_stats?).to eq(false)
104
+ expect(settings.interval).to eq(1)
105
+ end
106
+ end
107
+
108
+ context "when an error is raised while making a HTTP request" do
109
+ before do
110
+ allow(Net::HTTP).to receive(:get).and_raise(StandardError)
111
+ end
112
+
113
+ it "doesn't fetch remote settings" do
114
+ settings = nil
115
+ remote_settings = described_class.poll(project_id) do |data|
116
+ settings = data
117
+ end
118
+ sleep(0.1)
119
+ remote_settings.stop_polling
120
+
121
+ expect(a_request(:get, endpoint)).not_to have_been_made
122
+ expect(settings.interval).to eq(600)
123
+ end
124
+ end
125
+
126
+ context "when an error is raised while parsing returned JSON" do
127
+ before do
128
+ allow(JSON).to receive(:parse).and_raise(JSON::ParserError)
129
+ end
130
+
131
+ it "doesn't update settings data" do
132
+ settings = nil
133
+ remote_settings = described_class.poll(project_id) do |data|
134
+ settings = data
135
+ end
136
+ sleep(0.1)
137
+ remote_settings.stop_polling
138
+
139
+ expect(a_request(:get, endpoint)).to have_been_made.once
140
+ expect(settings.interval).to eq(600)
141
+ end
142
+ end
143
+
144
+ context "when API returns an XML response" do
145
+ before do
146
+ stub_request(:get, endpoint).to_return(status: 200, body: '<?xml ...')
147
+ end
148
+
149
+ it "doesn't update settings data" do
150
+ settings = nil
151
+ remote_settings = described_class.poll(project_id) do |data|
152
+ settings = data
153
+ end
154
+ sleep(0.1)
155
+ remote_settings.stop_polling
156
+
157
+ expect(a_request(:get, endpoint)).to have_been_made.once
158
+ expect(settings.interval).to eq(600)
159
+ end
160
+ end
161
+
162
+ context "when a config route is specified in the returned data" do
163
+ let(:new_endpoint) { 'http://example.com' }
164
+
165
+ let(:body) do
166
+ { 'config_route' => new_endpoint, 'poll_sec' => 0.1 }
167
+ end
168
+
169
+ before do
170
+ stub_request(:get, new_endpoint).to_return(status: 200, body: body.to_json)
171
+ end
172
+
173
+ it "makes the next request to the specified config route" do
174
+ remote_settings = described_class.poll(project_id) {}
175
+ sleep(0.2)
176
+
177
+ remote_settings.stop_polling
178
+
179
+ expect(a_request(:get, endpoint)).to have_been_made.once
180
+ expect(a_request(:get, new_endpoint)).to have_been_made.once
181
+ end
182
+ end
183
+ end
184
+
185
+ describe "#stop_polling" do
186
+ it "dumps config data to disk" do
187
+ expect(Dir).to receive(:mkdir).with(config_dir)
188
+ expect(File).to receive(:write).with(config_path, body.to_json)
189
+
190
+ remote_settings = described_class.poll(project_id) {}
191
+ sleep(0.2)
192
+ remote_settings.stop_polling
193
+
194
+ expect(a_request(:get, endpoint)).to have_been_made.once
195
+ end
196
+
197
+ context "when config dumping fails" do
198
+ it "logs an error" do
199
+ expect(File).to receive(:write).and_raise(StandardError)
200
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
201
+ '**Airbrake: config dumping failed: StandardError',
202
+ )
203
+
204
+ remote_settings = described_class.poll(project_id) {}
205
+ sleep(0.2)
206
+ remote_settings.stop_polling
207
+
208
+ expect(a_request(:get, endpoint)).to have_been_made.once
209
+ end
210
+ end
211
+ end
212
+ end