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.
- checksums.yaml +4 -4
- data/lib/airbrake-ruby.rb +23 -32
- data/lib/airbrake-ruby/async_sender.rb +1 -1
- data/lib/airbrake-ruby/config.rb +65 -13
- data/lib/airbrake-ruby/config/processor.rb +80 -0
- data/lib/airbrake-ruby/config/validator.rb +4 -0
- data/lib/airbrake-ruby/filter_chain.rb +15 -1
- data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +2 -1
- data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
- data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
- data/lib/airbrake-ruby/filters/keys_filter.rb +5 -5
- data/lib/airbrake-ruby/notice_notifier.rb +6 -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/thread_pool.rb +1 -0
- data/lib/airbrake-ruby/version.rb +1 -1
- data/spec/airbrake_spec.rb +71 -36
- data/spec/config/processor_spec.rb +223 -0
- data/spec/config/validator_spec.rb +18 -1
- data/spec/config_spec.rb +38 -6
- data/spec/filter_chain_spec.rb +27 -0
- data/spec/filters/git_last_checkout_filter_spec.rb +20 -3
- data/spec/filters/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +10 -10
- data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +10 -10
- data/spec/filters/sql_filter_spec.rb +3 -3
- data/spec/notice_notifier/options_spec.rb +4 -4
- data/spec/performance_notifier_spec.rb +2 -2
- 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
- data/spec/thread_pool_spec.rb +25 -5
- metadata +20 -12
@@ -82,6 +82,12 @@ module Airbrake
|
|
82
82
|
@context.merge!(context)
|
83
83
|
end
|
84
84
|
|
85
|
+
# @return [Boolean]
|
86
|
+
# @since v4.14.0
|
87
|
+
def has_filter?(filter_class) # rubocop:disable Naming/PredicateName
|
88
|
+
@filter_chain.includes?(filter_class)
|
89
|
+
end
|
90
|
+
|
85
91
|
private
|
86
92
|
|
87
93
|
def convert_to_exception(ex)
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# RemoteSettings polls the remote config of the passed project at fixed
|
3
|
+
# intervals. The fetched config is yielded as a callback parameter so that the
|
4
|
+
# invoker can define read config values.
|
5
|
+
#
|
6
|
+
# @example Disable/enable error notifications based on the remote value
|
7
|
+
# RemoteSettings.poll do |data|
|
8
|
+
# config.error_notifications = data.error_notifications?
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# When {#poll} is called, it will try to load remote settings from disk, so
|
12
|
+
# that it doesn't wait on the result from the API call.
|
13
|
+
#
|
14
|
+
# When {#stop_polling} is called, the current config will be dumped to disk.
|
15
|
+
#
|
16
|
+
# @since ?.?.?
|
17
|
+
# @api private
|
18
|
+
class RemoteSettings
|
19
|
+
include Airbrake::Loggable
|
20
|
+
|
21
|
+
# @return [String] the path to the persistent config
|
22
|
+
CONFIG_DUMP_PATH = File.join(
|
23
|
+
File.expand_path(__dir__),
|
24
|
+
'../../config/config.json',
|
25
|
+
).freeze
|
26
|
+
|
27
|
+
# Polls remote config of the given project.
|
28
|
+
#
|
29
|
+
# @param [Integer] project_id
|
30
|
+
# @yield [data]
|
31
|
+
# @yieldparam data [Airbrake::RemoteSettings::SettingsData]
|
32
|
+
# @return [Airbrake::RemoteSettings]
|
33
|
+
def self.poll(project_id, &block)
|
34
|
+
new(project_id, &block).poll
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [Integer] project_id
|
38
|
+
# @yield [data]
|
39
|
+
# @yieldparam data [Airbrake::RemoteSettings::SettingsData]
|
40
|
+
def initialize(project_id, &block)
|
41
|
+
@data = SettingsData.new(project_id, {})
|
42
|
+
@block = block
|
43
|
+
@poll = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
# Polls remote config of the given project in background. Loads local config
|
47
|
+
# first (if exists).
|
48
|
+
#
|
49
|
+
# @return [self]
|
50
|
+
def poll
|
51
|
+
@poll ||= Thread.new do
|
52
|
+
begin
|
53
|
+
load_config
|
54
|
+
rescue StandardError => ex
|
55
|
+
logger.error("#{LOG_LABEL} config loading failed: #{ex}")
|
56
|
+
end
|
57
|
+
|
58
|
+
@block.call(@data)
|
59
|
+
|
60
|
+
loop do
|
61
|
+
@block.call(@data.merge!(fetch_config))
|
62
|
+
sleep(@data.interval)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Stops the background poller thread. Dumps current config to disk.
|
70
|
+
#
|
71
|
+
# @return [void]
|
72
|
+
def stop_polling
|
73
|
+
@poll.kill if @poll
|
74
|
+
|
75
|
+
begin
|
76
|
+
dump_config
|
77
|
+
rescue StandardError => ex
|
78
|
+
logger.error("#{LOG_LABEL} config dumping failed: #{ex}")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def fetch_config
|
85
|
+
response = nil
|
86
|
+
begin
|
87
|
+
response = Net::HTTP.get(URI(@data.config_route))
|
88
|
+
rescue StandardError => ex
|
89
|
+
logger.error(ex)
|
90
|
+
return {}
|
91
|
+
end
|
92
|
+
|
93
|
+
# AWS S3 API returns XML when request is not valid. In this case we just
|
94
|
+
# print the returned body and exit the method.
|
95
|
+
if response.start_with?('<?xml ')
|
96
|
+
logger.error(response)
|
97
|
+
return {}
|
98
|
+
end
|
99
|
+
|
100
|
+
json = nil
|
101
|
+
begin
|
102
|
+
json = JSON.parse(response)
|
103
|
+
rescue JSON::ParserError => ex
|
104
|
+
logger.error(ex)
|
105
|
+
return {}
|
106
|
+
end
|
107
|
+
|
108
|
+
json
|
109
|
+
end
|
110
|
+
|
111
|
+
def load_config
|
112
|
+
config_dir = File.dirname(CONFIG_DUMP_PATH)
|
113
|
+
Dir.mkdir(config_dir) unless File.directory?(config_dir)
|
114
|
+
|
115
|
+
return unless File.exist?(CONFIG_DUMP_PATH)
|
116
|
+
|
117
|
+
config = File.read(CONFIG_DUMP_PATH)
|
118
|
+
@data.merge!(JSON.parse(config))
|
119
|
+
end
|
120
|
+
|
121
|
+
def dump_config
|
122
|
+
config_dir = File.dirname(CONFIG_DUMP_PATH)
|
123
|
+
Dir.mkdir(config_dir) unless File.directory?(config_dir)
|
124
|
+
|
125
|
+
File.write(CONFIG_DUMP_PATH, JSON.dump(@data.to_h))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Airbrake
|
2
|
+
class RemoteSettings
|
3
|
+
# SettingsData is a container, which wraps JSON payload returned by the
|
4
|
+
# remote settings API. It exposes the payload via convenient methods and
|
5
|
+
# also ensures that in case some data from the payload is missing, a default
|
6
|
+
# value would be returned instead.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # Create the object and pass initial data (empty hash).
|
10
|
+
# settings_data = SettingsData.new({})
|
11
|
+
#
|
12
|
+
# settings_data.interval #=> 600
|
13
|
+
#
|
14
|
+
# @since ?.?.?
|
15
|
+
# @api private
|
16
|
+
class SettingsData
|
17
|
+
# @return [Integer] how frequently we should poll the config API
|
18
|
+
DEFAULT_INTERVAL = 600
|
19
|
+
|
20
|
+
# @return [String] API version of the S3 API to poll
|
21
|
+
API_VER = '2020-06-18'.freeze
|
22
|
+
|
23
|
+
# @return [String] what URL to poll
|
24
|
+
CONFIG_ROUTE_PATTERN =
|
25
|
+
'https://%<bucket>s.s3.amazonaws.com/' \
|
26
|
+
"#{API_VER}/config/%<project_id>s/config.json".freeze
|
27
|
+
|
28
|
+
# @return [Hash{Symbol=>String}] the hash of all supported settings where
|
29
|
+
# the value is the name of the setting returned by the API
|
30
|
+
SETTINGS = {
|
31
|
+
errors: 'errors',
|
32
|
+
apm: 'apm',
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
# @param [Integer] project_id
|
36
|
+
# @param [Hash{String=>Object}] data
|
37
|
+
def initialize(project_id, data)
|
38
|
+
@project_id = project_id
|
39
|
+
@data = data
|
40
|
+
end
|
41
|
+
|
42
|
+
# Merges the given +hash+ with internal data.
|
43
|
+
#
|
44
|
+
# @param [Hash{String=>Object}] hash
|
45
|
+
# @return [self]
|
46
|
+
def merge!(hash)
|
47
|
+
@data.merge!(hash)
|
48
|
+
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Integer] how frequently we should poll for the config
|
53
|
+
def interval
|
54
|
+
return DEFAULT_INTERVAL if !@data.key?('poll_sec') || !@data['poll_sec']
|
55
|
+
|
56
|
+
@data['poll_sec'] > 0 ? @data['poll_sec'] : DEFAULT_INTERVAL
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [String] where the config is stored on S3.
|
60
|
+
def config_route
|
61
|
+
if !@data.key?('config_route') || !@data['config_route']
|
62
|
+
return format(
|
63
|
+
CONFIG_ROUTE_PATTERN,
|
64
|
+
bucket: 'staging-notifier-configs',
|
65
|
+
project_id: @project_id,
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
@data['config_route']
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [Boolean] whether error notifications are enabled
|
73
|
+
def error_notifications?
|
74
|
+
return true unless (s = find_setting(SETTINGS[:errors]))
|
75
|
+
|
76
|
+
s['enabled']
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [Boolean] whether APM is enabled
|
80
|
+
def performance_stats?
|
81
|
+
return true unless (s = find_setting(SETTINGS[:apm]))
|
82
|
+
|
83
|
+
s['enabled']
|
84
|
+
end
|
85
|
+
|
86
|
+
# @return [String, nil] the host, which provides the API endpoint to which
|
87
|
+
# exceptions should be sent
|
88
|
+
def error_host
|
89
|
+
return unless (s = find_setting(SETTINGS[:errors]))
|
90
|
+
|
91
|
+
s['endpoint']
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return [String, nil] the host, which provides the API endpoint to which
|
95
|
+
# APM data should be sent
|
96
|
+
def apm_host
|
97
|
+
return unless (s = find_setting(SETTINGS[:apm]))
|
98
|
+
|
99
|
+
s['endpoint']
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [Hash{String=>Object}] raw representation of JSON payload
|
103
|
+
def to_h
|
104
|
+
@data.dup
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def find_setting(name)
|
110
|
+
return unless @data.key?('settings')
|
111
|
+
|
112
|
+
@data['settings'].find { |s| s['name'] == name }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -23,7 +23,7 @@ module Airbrake
|
|
23
23
|
# @param [#to_json] data
|
24
24
|
# @param [URI::HTTPS] endpoint
|
25
25
|
# @return [Hash{String=>String}] the parsed HTTP response
|
26
|
-
def send(data, promise, endpoint = @config.
|
26
|
+
def send(data, promise, endpoint = @config.error_endpoint)
|
27
27
|
return promise if rate_limited_ip?(promise)
|
28
28
|
|
29
29
|
response = nil
|
data/spec/airbrake_spec.rb
CHANGED
@@ -1,4 +1,13 @@
|
|
1
1
|
RSpec.describe Airbrake do
|
2
|
+
let(:remote_settings) { instance_double(Airbrake::RemoteSettings) }
|
3
|
+
|
4
|
+
before do
|
5
|
+
allow(Airbrake::RemoteSettings).to receive(:poll).and_return(remote_settings)
|
6
|
+
allow(remote_settings).to receive(:stop_polling)
|
7
|
+
end
|
8
|
+
|
9
|
+
after { described_class.instance_variable_set(:@remote_settings, nil) }
|
10
|
+
|
2
11
|
it "gets initialized with a performance notifier" do
|
3
12
|
expect(described_class.performance_notifier).not_to be_nil
|
4
13
|
end
|
@@ -85,43 +94,55 @@ RSpec.describe Airbrake do
|
|
85
94
|
expect(described_class.notice_notifier).not_to receive(:add_filter)
|
86
95
|
10.times { described_class.configure {} }
|
87
96
|
end
|
97
|
+
|
98
|
+
it "appends some default filters" do
|
99
|
+
allow(described_class.notice_notifier).to receive(:add_filter)
|
100
|
+
expect(described_class.notice_notifier).to receive(:add_filter).with(
|
101
|
+
an_instance_of(Airbrake::Filters::RootDirectoryFilter),
|
102
|
+
)
|
103
|
+
|
104
|
+
described_class.configure do |c|
|
105
|
+
c.project_id = 1
|
106
|
+
c.project_key = '2'
|
107
|
+
end
|
108
|
+
end
|
88
109
|
end
|
89
110
|
|
90
|
-
context "when
|
91
|
-
before { allow(
|
111
|
+
context "when blocklist_keys gets configured" do
|
112
|
+
before { allow(described_class.notice_notifier).to receive(:add_filter) }
|
92
113
|
|
93
|
-
it "adds
|
94
|
-
expect(
|
95
|
-
.with(an_instance_of(Airbrake::Filters::
|
96
|
-
described_class.configure { |c| c.
|
114
|
+
it "adds blocklist filter" do
|
115
|
+
expect(described_class.notice_notifier).to receive(:add_filter)
|
116
|
+
.with(an_instance_of(Airbrake::Filters::KeysBlocklist))
|
117
|
+
described_class.configure { |c| c.blocklist_keys = %w[password] }
|
97
118
|
end
|
98
119
|
|
99
|
-
it "initializes
|
100
|
-
expect(Airbrake::Filters::
|
101
|
-
described_class.configure { |c| c.
|
120
|
+
it "initializes blocklist with specified parameters" do
|
121
|
+
expect(Airbrake::Filters::KeysBlocklist).to receive(:new).with(%w[password])
|
122
|
+
described_class.configure { |c| c.blocklist_keys = %w[password] }
|
102
123
|
end
|
103
124
|
end
|
104
125
|
|
105
|
-
context "when
|
106
|
-
before { allow(
|
126
|
+
context "when allowlist_keys gets configured" do
|
127
|
+
before { allow(described_class.notice_notifier).to receive(:add_filter) }
|
107
128
|
|
108
|
-
it "adds
|
109
|
-
expect(
|
110
|
-
.with(an_instance_of(Airbrake::Filters::
|
111
|
-
described_class.configure { |c| c.
|
129
|
+
it "adds allowlist filter" do
|
130
|
+
expect(described_class.notice_notifier).to receive(:add_filter)
|
131
|
+
.with(an_instance_of(Airbrake::Filters::KeysAllowlist))
|
132
|
+
described_class.configure { |c| c.allowlist_keys = %w[banana] }
|
112
133
|
end
|
113
134
|
|
114
|
-
it "initializes
|
115
|
-
expect(Airbrake::Filters::
|
116
|
-
described_class.configure { |c| c.
|
135
|
+
it "initializes allowlist with specified parameters" do
|
136
|
+
expect(Airbrake::Filters::KeysAllowlist).to receive(:new).with(%w[banana])
|
137
|
+
described_class.configure { |c| c.allowlist_keys = %w[banana] }
|
117
138
|
end
|
118
139
|
end
|
119
140
|
|
120
141
|
context "when root_directory gets configured" do
|
121
|
-
before { allow(
|
142
|
+
before { allow(described_class.notice_notifier).to receive(:add_filter) }
|
122
143
|
|
123
144
|
it "adds root directory filter" do
|
124
|
-
expect(
|
145
|
+
expect(described_class.notice_notifier).to receive(:add_filter)
|
125
146
|
.with(an_instance_of(Airbrake::Filters::RootDirectoryFilter))
|
126
147
|
described_class.configure { |c| c.root_directory = '/my/path' }
|
127
148
|
end
|
@@ -133,7 +154,7 @@ RSpec.describe Airbrake do
|
|
133
154
|
end
|
134
155
|
|
135
156
|
it "adds git revision filter" do
|
136
|
-
expect(
|
157
|
+
expect(described_class.notice_notifier).to receive(:add_filter)
|
137
158
|
.with(an_instance_of(Airbrake::Filters::GitRevisionFilter))
|
138
159
|
described_class.configure { |c| c.root_directory = '/my/path' }
|
139
160
|
end
|
@@ -145,7 +166,7 @@ RSpec.describe Airbrake do
|
|
145
166
|
end
|
146
167
|
|
147
168
|
it "adds git repository filter" do
|
148
|
-
expect(
|
169
|
+
expect(described_class.notice_notifier).to receive(:add_filter)
|
149
170
|
.with(an_instance_of(Airbrake::Filters::GitRepositoryFilter))
|
150
171
|
described_class.configure { |c| c.root_directory = '/my/path' }
|
151
172
|
end
|
@@ -157,7 +178,7 @@ RSpec.describe Airbrake do
|
|
157
178
|
end
|
158
179
|
|
159
180
|
it "adds git last checkout filter" do
|
160
|
-
expect(
|
181
|
+
expect(described_class.notice_notifier).to receive(:add_filter)
|
161
182
|
.with(an_instance_of(Airbrake::Filters::GitLastCheckoutFilter))
|
162
183
|
described_class.configure { |c| c.root_directory = '/my/path' }
|
163
184
|
end
|
@@ -170,7 +191,7 @@ RSpec.describe Airbrake do
|
|
170
191
|
end
|
171
192
|
end
|
172
193
|
|
173
|
-
describe "
|
194
|
+
describe ".notify_request" do
|
174
195
|
context "when :stash key is not provided" do
|
175
196
|
it "doesn't add anything to the stash of the request" do
|
176
197
|
expect(described_class.performance_notifier).to receive(:notify) do |request|
|
@@ -205,7 +226,7 @@ RSpec.describe Airbrake do
|
|
205
226
|
end
|
206
227
|
end
|
207
228
|
|
208
|
-
describe "
|
229
|
+
describe ".notify_request_sync" do
|
209
230
|
it "notifies request synchronously" do
|
210
231
|
expect(described_class.performance_notifier).to receive(:notify_sync)
|
211
232
|
|
@@ -221,7 +242,7 @@ RSpec.describe Airbrake do
|
|
221
242
|
end
|
222
243
|
end
|
223
244
|
|
224
|
-
describe "
|
245
|
+
describe ".notify_query" do
|
225
246
|
context "when :stash key is not provided" do
|
226
247
|
it "doesn't add anything to the stash of the query" do
|
227
248
|
expect(described_class.performance_notifier).to receive(:notify) do |query|
|
@@ -256,7 +277,7 @@ RSpec.describe Airbrake do
|
|
256
277
|
end
|
257
278
|
end
|
258
279
|
|
259
|
-
describe "
|
280
|
+
describe ".notify_query_sync" do
|
260
281
|
it "notifies query synchronously" do
|
261
282
|
expect(described_class.performance_notifier).to receive(:notify_sync)
|
262
283
|
|
@@ -272,7 +293,7 @@ RSpec.describe Airbrake do
|
|
272
293
|
end
|
273
294
|
end
|
274
295
|
|
275
|
-
describe "
|
296
|
+
describe ".notify_performance_breakdown" do
|
276
297
|
context "when :stash key is not provided" do
|
277
298
|
it "doesn't add anything to the stash of the performance breakdown" do
|
278
299
|
expect(described_class.performance_notifier).to receive(:notify) do |query|
|
@@ -310,7 +331,7 @@ RSpec.describe Airbrake do
|
|
310
331
|
end
|
311
332
|
end
|
312
333
|
|
313
|
-
describe "
|
334
|
+
describe ".notify_performance_breakdown_sync" do
|
314
335
|
it "notifies performance breakdown synchronously" do
|
315
336
|
expect(described_class.performance_notifier).to receive(:notify_sync)
|
316
337
|
|
@@ -327,7 +348,7 @@ RSpec.describe Airbrake do
|
|
327
348
|
end
|
328
349
|
end
|
329
350
|
|
330
|
-
describe "
|
351
|
+
describe ".notify_queue" do
|
331
352
|
context "when :stash key is not provided" do
|
332
353
|
it "doesn't add anything to the stash of the queue" do
|
333
354
|
expect(described_class.performance_notifier).to receive(:notify) do |queue|
|
@@ -358,7 +379,7 @@ RSpec.describe Airbrake do
|
|
358
379
|
end
|
359
380
|
end
|
360
381
|
|
361
|
-
describe "
|
382
|
+
describe ".notify_queue_sync" do
|
362
383
|
it "notifies queue synchronously" do
|
363
384
|
expect(described_class.performance_notifier).to receive(:notify_sync)
|
364
385
|
|
@@ -392,33 +413,47 @@ RSpec.describe Airbrake do
|
|
392
413
|
end
|
393
414
|
|
394
415
|
describe ".close" do
|
395
|
-
after {
|
416
|
+
after { described_class.reset }
|
396
417
|
|
397
418
|
context "when notice_notifier is defined" do
|
398
419
|
it "gets closed" do
|
399
|
-
expect(
|
420
|
+
expect(described_class.notice_notifier).to receive(:close)
|
400
421
|
end
|
401
422
|
end
|
402
423
|
|
403
424
|
context "when notice_notifier is undefined" do
|
404
425
|
it "doesn't get closed (because it wasn't initialized)" do
|
405
|
-
|
426
|
+
described_class.instance_variable_set(:@notice_notifier, nil)
|
406
427
|
expect_any_instance_of(Airbrake::NoticeNotifier).not_to receive(:close)
|
407
428
|
end
|
408
429
|
end
|
409
430
|
|
410
431
|
context "when performance_notifier is defined" do
|
411
432
|
it "gets closed" do
|
412
|
-
expect(
|
433
|
+
expect(described_class.performance_notifier).to receive(:close)
|
413
434
|
end
|
414
435
|
end
|
415
436
|
|
416
437
|
context "when perforance_notifier is undefined" do
|
417
438
|
it "doesn't get closed (because it wasn't initialized)" do
|
418
|
-
|
439
|
+
described_class.instance_variable_set(:@performance_notifier, nil)
|
419
440
|
expect_any_instance_of(Airbrake::PerformanceNotifier)
|
420
441
|
.not_to receive(:close)
|
421
442
|
end
|
422
443
|
end
|
444
|
+
|
445
|
+
context "when remote settings are defined" do
|
446
|
+
it "stops polling" do
|
447
|
+
described_class.instance_variable_set(:@remote_settings, remote_settings)
|
448
|
+
expect(remote_settings).to receive(:stop_polling)
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
context "when remote settings are undefined" do
|
453
|
+
it "doesn't stop polling (because they weren't initialized)" do
|
454
|
+
described_class.instance_variable_set(:@remote_settings, nil)
|
455
|
+
expect(remote_settings).not_to receive(:stop_polling)
|
456
|
+
end
|
457
|
+
end
|
423
458
|
end
|
424
459
|
end
|