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.
- checksums.yaml +4 -4
- data/lib/airbrake-ruby.rb +21 -33
- data/lib/airbrake-ruby/async_sender.rb +1 -1
- data/lib/airbrake-ruby/config.rb +34 -6
- data/lib/airbrake-ruby/config/processor.rb +80 -0
- data/lib/airbrake-ruby/config/validator.rb +4 -0
- data/lib/airbrake-ruby/performance_notifier.rb +1 -1
- data/lib/airbrake-ruby/remote_settings.rb +128 -0
- data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
- data/lib/airbrake-ruby/sync_sender.rb +1 -1
- data/lib/airbrake-ruby/version.rb +1 -1
- data/spec/airbrake_spec.rb +45 -22
- data/spec/config/processor_spec.rb +223 -0
- data/spec/config/validator_spec.rb +18 -1
- data/spec/config_spec.rb +8 -4
- data/spec/remote_settings/settings_data_spec.rb +327 -0
- data/spec/remote_settings_spec.rb +212 -0
- data/spec/sync_sender_spec.rb +3 -1
- metadata +13 -4
data/spec/config_spec.rb
CHANGED
@@ -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(:
|
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 "#
|
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(:
|
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(:
|
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
|