airbrake-ruby 4.13.4 → 5.0.0.rc.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) 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/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
  9. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  10. data/lib/airbrake-ruby/filters/keys_filter.rb +5 -5
  11. data/lib/airbrake-ruby/notice.rb +1 -8
  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 +143 -0
  15. data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
  16. data/lib/airbrake-ruby/sync_sender.rb +2 -2
  17. data/lib/airbrake-ruby/thread_pool.rb +1 -0
  18. data/lib/airbrake-ruby/version.rb +11 -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/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +10 -10
  25. data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +10 -10
  26. data/spec/filters/sql_filter_spec.rb +3 -3
  27. data/spec/notice_notifier/options_spec.rb +4 -4
  28. data/spec/performance_notifier_spec.rb +2 -2
  29. data/spec/remote_settings/settings_data_spec.rb +327 -0
  30. data/spec/remote_settings_spec.rb +230 -0
  31. data/spec/sync_sender_spec.rb +3 -1
  32. data/spec/thread_pool_spec.rb +25 -5
  33. metadata +22 -13
@@ -0,0 +1,223 @@
1
+ RSpec.describe Airbrake::Config::Processor do
2
+ let(:notifier) { Airbrake::NoticeNotifier.new }
3
+
4
+ describe "#process_blocklist" do
5
+ let(:config) { Airbrake::Config.new(blocklist_keys: %w[a b c]) }
6
+
7
+ context "when there ARE blocklist keys" do
8
+ it "adds the blocklist filter" do
9
+ described_class.new(config).process_blocklist(notifier)
10
+ expect(notifier.has_filter?(Airbrake::Filters::KeysBlocklist)).to eq(true)
11
+ end
12
+ end
13
+
14
+ context "when there are NO blocklist keys" do
15
+ let(:config) { Airbrake::Config.new(blocklist_keys: %w[]) }
16
+
17
+ it "doesn't add the blocklist filter" do
18
+ described_class.new(config).process_blocklist(notifier)
19
+ expect(notifier.has_filter?(Airbrake::Filters::KeysBlocklist))
20
+ .to eq(false)
21
+ end
22
+ end
23
+ end
24
+
25
+ describe "#process_allowlist" do
26
+ let(:config) { Airbrake::Config.new(allowlist_keys: %w[a b c]) }
27
+
28
+ context "when there ARE allowlist keys" do
29
+ it "adds the allowlist filter" do
30
+ described_class.new(config).process_allowlist(notifier)
31
+ expect(notifier.has_filter?(Airbrake::Filters::KeysAllowlist)).to eq(true)
32
+ end
33
+ end
34
+
35
+ context "when there are NO allowlist keys" do
36
+ let(:config) { Airbrake::Config.new(allowlist_keys: %w[]) }
37
+
38
+ it "doesn't add the allowlist filter" do
39
+ described_class.new(config).process_allowlist(notifier)
40
+ expect(notifier.has_filter?(Airbrake::Filters::KeysAllowlist))
41
+ .to eq(false)
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "#process_remote_configuration" do
47
+ context "when the config doesn't define a project_id" do
48
+ let(:config) { Airbrake::Config.new(project_id: nil) }
49
+
50
+ it "doesn't set remote settings" do
51
+ expect(Airbrake::RemoteSettings).not_to receive(:poll)
52
+ described_class.new(config).process_remote_configuration
53
+ end
54
+ end
55
+
56
+ context "when the config defines a project_id" do
57
+ context "and when remote configuration is false" do
58
+ let(:config) do
59
+ Airbrake::Config.new(project_id: 123, __remote_configuration: false)
60
+ end
61
+
62
+ it "doesn't set remote settings" do
63
+ expect(Airbrake::RemoteSettings).not_to receive(:poll)
64
+ described_class.new(config).process_remote_configuration
65
+ end
66
+ end
67
+
68
+ context "and when remote configuration is true" do
69
+ let(:config) do
70
+ Airbrake::Config.new(project_id: 123, __remote_configuration: true)
71
+ end
72
+
73
+ it "sets remote settings" do
74
+ expect(Airbrake::RemoteSettings).to receive(:poll)
75
+ described_class.new(config).process_remote_configuration
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ describe "#add_filters" do
82
+ context "when there's a root directory" do
83
+ let(:config) { Airbrake::Config.new(root_directory: '/abc') }
84
+
85
+ it "adds RootDirectoryFilter" do
86
+ described_class.new(config).add_filters(notifier)
87
+ expect(notifier.has_filter?(Airbrake::Filters::RootDirectoryFilter))
88
+ .to eq(true)
89
+ end
90
+
91
+ it "adds GitRevisionFilter" do
92
+ described_class.new(config).add_filters(notifier)
93
+ expect(notifier.has_filter?(Airbrake::Filters::GitRevisionFilter))
94
+ .to eq(true)
95
+ end
96
+
97
+ it "adds GitRepositoryFilter" do
98
+ described_class.new(config).add_filters(notifier)
99
+ expect(notifier.has_filter?(Airbrake::Filters::GitRepositoryFilter))
100
+ .to eq(true)
101
+ end
102
+
103
+ it "adds GitLastCheckoutFilter" do
104
+ described_class.new(config).add_filters(notifier)
105
+ expect(notifier.has_filter?(Airbrake::Filters::GitLastCheckoutFilter))
106
+ .to eq(true)
107
+ end
108
+ end
109
+
110
+ context "when there's NO root directory" do
111
+ let(:config) { Airbrake::Config.new(root_directory: nil) }
112
+
113
+ it "doesn't add RootDirectoryFilter" do
114
+ described_class.new(config).add_filters(notifier)
115
+ expect(notifier.has_filter?(Airbrake::Filters::RootDirectoryFilter))
116
+ .to eq(false)
117
+ end
118
+
119
+ it "doesn't add GitRevisionFilter" do
120
+ described_class.new(config).add_filters(notifier)
121
+ expect(notifier.has_filter?(Airbrake::Filters::GitRevisionFilter))
122
+ .to eq(false)
123
+ end
124
+
125
+ it "doesn't add GitRepositoryFilter" do
126
+ described_class.new(config).add_filters(notifier)
127
+ expect(notifier.has_filter?(Airbrake::Filters::GitRepositoryFilter))
128
+ .to eq(false)
129
+ end
130
+
131
+ it "doesn't add GitLastCheckoutFilter" do
132
+ described_class.new(config).add_filters(notifier)
133
+ expect(notifier.has_filter?(Airbrake::Filters::GitLastCheckoutFilter))
134
+ .to eq(false)
135
+ end
136
+ end
137
+ end
138
+
139
+ describe "#poll_callback" do
140
+ let(:logger) { Logger.new(File::NULL) }
141
+
142
+ let(:config) do
143
+ Airbrake::Config.new(
144
+ project_id: 123,
145
+ __remote_configuration: true,
146
+ logger: logger,
147
+ )
148
+ end
149
+
150
+ let(:data) do
151
+ instance_double(Airbrake::RemoteSettings::SettingsData)
152
+ end
153
+
154
+ before do
155
+ allow(data).to receive(:to_h)
156
+ allow(data).to receive(:error_host)
157
+ allow(data).to receive(:apm_host)
158
+ allow(data).to receive(:error_notifications?)
159
+ allow(data).to receive(:performance_stats?)
160
+ end
161
+
162
+ it "logs given data" do
163
+ expect(logger).to receive(:debug).with(/applying remote settings/)
164
+ described_class.new(config).poll_callback(data)
165
+ end
166
+
167
+ it "sets the error_notifications option" do
168
+ config.error_notifications = false
169
+ expect(data).to receive(:error_notifications?).and_return(true)
170
+
171
+ described_class.new(config).poll_callback(data)
172
+ expect(config.error_notifications).to eq(true)
173
+ end
174
+
175
+ it "sets the performance_stats option" do
176
+ config.performance_stats = false
177
+ expect(data).to receive(:performance_stats?).and_return(true)
178
+
179
+ described_class.new(config).poll_callback(data)
180
+ expect(config.performance_stats).to eq(true)
181
+ end
182
+
183
+ context "when error_host returns a value" do
184
+ it "sets the error_host option" do
185
+ config.error_host = 'http://api.airbrake.io'
186
+ allow(data).to receive(:error_host).and_return('https://api.example.com')
187
+
188
+ described_class.new(config).poll_callback(data)
189
+ expect(config.error_host).to eq('https://api.example.com')
190
+ end
191
+ end
192
+
193
+ context "when error_host returns nil" do
194
+ it "doesn't modify the error_host option" do
195
+ config.error_host = 'http://api.airbrake.io'
196
+ allow(data).to receive(:error_host).and_return(nil)
197
+
198
+ described_class.new(config).poll_callback(data)
199
+ expect(config.error_host).to eq('http://api.airbrake.io')
200
+ end
201
+ end
202
+
203
+ context "when apm_host returns a value" do
204
+ it "sets the apm_host option" do
205
+ config.apm_host = 'http://api.airbrake.io'
206
+ allow(data).to receive(:apm_host).and_return('https://api.example.com')
207
+
208
+ described_class.new(config).poll_callback(data)
209
+ expect(config.apm_host).to eq('https://api.example.com')
210
+ end
211
+ end
212
+
213
+ context "when apm_host returns nil" do
214
+ it "doesn't modify the apm_host option" do
215
+ config.apm_host = 'http://api.airbrake.io'
216
+ allow(data).to receive(:apm_host).and_return(nil)
217
+
218
+ described_class.new(config).poll_callback(data)
219
+ expect(config.apm_host).to eq('http://api.airbrake.io')
220
+ end
221
+ end
222
+ end
223
+ end
@@ -169,7 +169,7 @@ RSpec.describe Airbrake::Config::Validator do
169
169
  }
170
170
  end
171
171
 
172
- it "returns a rejected promise" do
172
+ it "returns a resolved promise" do
173
173
  promise = described_class.check_notify_ability(config)
174
174
  expect(promise).to be_resolved
175
175
  end
@@ -180,5 +180,22 @@ RSpec.describe Airbrake::Config::Validator do
180
180
  described_class.check_notify_ability(config)
181
181
  end
182
182
  end
183
+
184
+ context "when the error_notifications option is false" do
185
+ let(:config_params) do
186
+ {
187
+ project_id: valid_id,
188
+ project_key: valid_key,
189
+ error_notifications: false,
190
+ }
191
+ end
192
+
193
+ it "returns a rejected promise" do
194
+ promise = described_class.check_notify_ability(config)
195
+ expect(promise.value).to eq(
196
+ 'error' => "error notifications are disabled",
197
+ )
198
+ end
199
+ end
183
200
  end
184
201
  end
@@ -10,19 +10,23 @@ RSpec.describe Airbrake::Config do
10
10
  its(:app_version) { is_expected.to be_nil }
11
11
  its(:versions) { is_expected.to be_empty }
12
12
  its(:host) { is_expected.to eq('https://api.airbrake.io') }
13
- its(:endpoint) { is_expected.not_to be_nil }
13
+ its(:error_host) { is_expected.to eq('https://api.airbrake.io') }
14
+ its(:apm_host) { is_expected.to eq('https://api.airbrake.io') }
15
+ its(:error_endpoint) { is_expected.not_to be_nil }
14
16
  its(:workers) { is_expected.to eq(1) }
15
17
  its(:queue_size) { is_expected.to eq(100) }
16
18
  its(:root_directory) { is_expected.to eq(Bundler.root.realpath.to_s) }
17
19
  its(:environment) { is_expected.to be_nil }
18
20
  its(:ignore_environments) { is_expected.to be_empty }
19
21
  its(:timeout) { is_expected.to be_nil }
20
- its(:blacklist_keys) { is_expected.to be_empty }
21
- its(:whitelist_keys) { is_expected.to be_empty }
22
+ its(:blocklist_keys) { is_expected.to be_empty }
23
+ its(:allowlist_keys) { is_expected.to be_empty }
22
24
  its(:performance_stats) { is_expected.to eq(true) }
23
25
  its(:performance_stats_flush_period) { is_expected.to eq(15) }
24
26
  its(:query_stats) { is_expected.to eq(true) }
25
27
  its(:job_stats) { is_expected.to eq(true) }
28
+ its(:error_notifications) { is_expected.to eq(true) }
29
+ its(:__remote_configuration) { is_expected.to eq(false) }
26
30
 
27
31
  describe "#new" do
28
32
  context "when user config is passed" do
@@ -63,13 +67,13 @@ RSpec.describe Airbrake::Config do
63
67
  end
64
68
  end
65
69
 
66
- describe "#endpoint" do
70
+ describe "#error_endpoint" do
67
71
  subject { described_class.new(valid_params.merge(user_config)) }
68
72
 
69
73
  context "when host ends with a URL with a slug with a trailing slash" do
70
74
  let(:user_config) { { host: 'https://localhost/bingo/' } }
71
75
 
72
- its(:endpoint) do
76
+ its(:error_endpoint) do
73
77
  is_expected.to eq(URI('https://localhost/bingo/api/v3/projects/1/notices'))
74
78
  end
75
79
  end
@@ -77,7 +81,7 @@ RSpec.describe Airbrake::Config do
77
81
  context "when host ends with a URL with a slug without a trailing slash" do
78
82
  let(:user_config) { { host: 'https://localhost/bingo' } }
79
83
 
80
- its(:endpoint) do
84
+ its(:error_endpoint) do
81
85
  is_expected.to eq(URI('https://localhost/api/v3/projects/1/notices'))
82
86
  end
83
87
  end
@@ -169,4 +173,32 @@ RSpec.describe Airbrake::Config do
169
173
  expect(subject.logger.level).to eq(Logger::WARN)
170
174
  end
171
175
  end
176
+
177
+ describe "#blacklist_keys=" do
178
+ before { allow(Kernel).to receive(:warn) }
179
+
180
+ it "sets blocklist_keys instead" do
181
+ subject.blacklist_keys = [1, 2, 3]
182
+ expect(subject.blocklist_keys).to eq([1, 2, 3])
183
+ end
184
+
185
+ it "prints a warning" do
186
+ expect(Kernel).to receive(:warn).with(/use blocklist_keys= instead/)
187
+ subject.blacklist_keys = [1, 2, 3]
188
+ end
189
+ end
190
+
191
+ describe "#whitelist_keys=" do
192
+ before { allow(Kernel).to receive(:warn) }
193
+
194
+ it "sets allowlist_keys instead" do
195
+ subject.whitelist_keys = [1, 2, 3]
196
+ expect(subject.allowlist_keys).to eq([1, 2, 3])
197
+ end
198
+
199
+ it "prints a warning" do
200
+ expect(Kernel).to receive(:warn).with(/use allowlist_keys= instead/)
201
+ subject.whitelist_keys = [1, 2, 3]
202
+ end
203
+ end
172
204
  end
@@ -89,4 +89,31 @@ RSpec.describe Airbrake::FilterChain do
89
89
  expect(subject.inspect).to eq('[Proc]')
90
90
  end
91
91
  end
92
+
93
+ describe "#includes?" do
94
+ context "when a custom filter class is included in the filter chain" do
95
+ it "returns true" do
96
+ klass = Class.new {}
97
+
98
+ subject.add_filter(klass.new)
99
+ expect(subject.includes?(klass)).to eq(true)
100
+ end
101
+ end
102
+
103
+ context "when Proc filter class is included in the filter chain" do
104
+ it "returns true" do
105
+ subject.add_filter(proc {})
106
+ expect(subject.includes?(Proc)).to eq(true)
107
+ end
108
+ end
109
+
110
+ context "when filter class is NOT included in the filter chain" do
111
+ it "returns false" do
112
+ klass = Class.new {}
113
+
114
+ subject.add_filter(proc {})
115
+ expect(subject.includes?(klass)).to eq(false)
116
+ end
117
+ end
118
+ end
92
119
  end
@@ -1,4 +1,4 @@
1
- RSpec.describe Airbrake::Filters::KeysWhitelist do
1
+ RSpec.describe Airbrake::Filters::KeysAllowlist do
2
2
  subject { described_class.new(patterns) }
3
3
 
4
4
  let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
@@ -70,10 +70,10 @@ RSpec.describe Airbrake::Filters::KeysWhitelist do
70
70
 
71
71
  it "logs an error" do
72
72
  expect(Airbrake::Loggable.instance).to receive(:error).with(
73
- /KeysWhitelist is invalid.+patterns: \[#<Object:.+>\]/,
73
+ /KeysAllowlist is invalid.+patterns: \[#<Object:.+>\]/,
74
74
  )
75
- keys_whitelist = described_class.new(patterns)
76
- keys_whitelist.call(notice)
75
+ keys_allowlist = described_class.new(patterns)
76
+ keys_allowlist.call(notice)
77
77
  end
78
78
  end
79
79
 
@@ -83,10 +83,10 @@ RSpec.describe Airbrake::Filters::KeysWhitelist do
83
83
  context "and when the filter is called once" do
84
84
  it "logs an error" do
85
85
  expect(Airbrake::Loggable.instance).to receive(:error).with(
86
- /KeysWhitelist is invalid.+patterns: \[#<Proc:.+>\]/,
86
+ /KeysAllowlist is invalid.+patterns: \[#<Proc:.+>\]/,
87
87
  )
88
- keys_whitelist = described_class.new(patterns)
89
- keys_whitelist.call(notice)
88
+ keys_allowlist = described_class.new(patterns)
89
+ keys_allowlist.call(notice)
90
90
  end
91
91
 
92
92
  include_examples(
@@ -113,10 +113,10 @@ RSpec.describe Airbrake::Filters::KeysWhitelist do
113
113
 
114
114
  it "logs an error" do
115
115
  expect(Airbrake::Loggable.instance).to receive(:error).with(
116
- /KeysWhitelist is invalid.+patterns: \[#<Object:.+>\]/,
116
+ /KeysAllowlist is invalid.+patterns: \[#<Object:.+>\]/,
117
117
  )
118
- keys_whitelist = described_class.new(patterns)
119
- keys_whitelist.call(notice)
118
+ keys_allowlist = described_class.new(patterns)
119
+ keys_allowlist.call(notice)
120
120
  end
121
121
  end
122
122
 
@@ -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