airbrake-ruby 4.15.0-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.
@@ -10,7 +10,9 @@ 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) }
@@ -23,6 +25,8 @@ RSpec.describe Airbrake::Config do
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
@@ -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