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.
- 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/{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.rb +1 -8
- 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 +143 -0
- data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
- data/lib/airbrake-ruby/sync_sender.rb +2 -2
- data/lib/airbrake-ruby/thread_pool.rb +1 -0
- data/lib/airbrake-ruby/version.rb +11 -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/{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 +230 -0
- data/spec/sync_sender_spec.rb +3 -1
- data/spec/thread_pool_spec.rb +25 -5
- metadata +22 -13
data/lib/airbrake-ruby/notice.rb
CHANGED
@@ -4,19 +4,12 @@ module Airbrake
|
|
4
4
|
#
|
5
5
|
# @since v1.0.0
|
6
6
|
class Notice
|
7
|
-
# @return [Hash{Symbol=>String}] the information about the notifier library
|
8
|
-
NOTIFIER = {
|
9
|
-
name: 'airbrake-ruby'.freeze,
|
10
|
-
version: Airbrake::AIRBRAKE_RUBY_VERSION,
|
11
|
-
url: 'https://github.com/airbrake/airbrake-ruby'.freeze,
|
12
|
-
}.freeze
|
13
|
-
|
14
7
|
# @return [Hash{Symbol=>String,Hash}] the information to be displayed in the
|
15
8
|
# Context tab in the dashboard
|
16
9
|
CONTEXT = {
|
17
10
|
os: RUBY_PLATFORM,
|
18
11
|
language: "#{RUBY_ENGINE}/#{RUBY_VERSION}".freeze,
|
19
|
-
notifier:
|
12
|
+
notifier: Airbrake::NOTIFIER_INFO,
|
20
13
|
}.freeze
|
21
14
|
|
22
15
|
# @return [Integer] the maxium size of the JSON payload in bytes
|
@@ -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,143 @@
|
|
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
|
+
# @return [Hash{Symbol=>String}] metadata to be attached to every GET
|
28
|
+
# request
|
29
|
+
QUERY_PARAMS = URI.encode_www_form(
|
30
|
+
notifier_name: Airbrake::NOTIFIER_INFO[:name],
|
31
|
+
notifier_version: Airbrake::NOTIFIER_INFO[:version],
|
32
|
+
os: RUBY_PLATFORM,
|
33
|
+
language: "#{RUBY_ENGINE}/#{RUBY_VERSION}".freeze,
|
34
|
+
).freeze
|
35
|
+
|
36
|
+
# Polls remote config of the given project.
|
37
|
+
#
|
38
|
+
# @param [Integer] project_id
|
39
|
+
# @yield [data]
|
40
|
+
# @yieldparam data [Airbrake::RemoteSettings::SettingsData]
|
41
|
+
# @return [Airbrake::RemoteSettings]
|
42
|
+
def self.poll(project_id, &block)
|
43
|
+
new(project_id, &block).poll
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param [Integer] project_id
|
47
|
+
# @yield [data]
|
48
|
+
# @yieldparam data [Airbrake::RemoteSettings::SettingsData]
|
49
|
+
def initialize(project_id, &block)
|
50
|
+
@data = SettingsData.new(project_id, {})
|
51
|
+
@block = block
|
52
|
+
@poll = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
# Polls remote config of the given project in background. Loads local config
|
56
|
+
# first (if exists).
|
57
|
+
#
|
58
|
+
# @return [self]
|
59
|
+
def poll
|
60
|
+
@poll ||= Thread.new do
|
61
|
+
begin
|
62
|
+
load_config
|
63
|
+
rescue StandardError => ex
|
64
|
+
logger.error("#{LOG_LABEL} config loading failed: #{ex}")
|
65
|
+
end
|
66
|
+
|
67
|
+
@block.call(@data)
|
68
|
+
|
69
|
+
loop do
|
70
|
+
@block.call(@data.merge!(fetch_config))
|
71
|
+
sleep(@data.interval)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
# Stops the background poller thread. Dumps current config to disk.
|
79
|
+
#
|
80
|
+
# @return [void]
|
81
|
+
def stop_polling
|
82
|
+
@poll.kill if @poll
|
83
|
+
|
84
|
+
begin
|
85
|
+
dump_config
|
86
|
+
rescue StandardError => ex
|
87
|
+
logger.error("#{LOG_LABEL} config dumping failed: #{ex}")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def fetch_config
|
94
|
+
response = nil
|
95
|
+
begin
|
96
|
+
response = Net::HTTP.get(build_config_uri)
|
97
|
+
rescue StandardError => ex
|
98
|
+
logger.error(ex)
|
99
|
+
return {}
|
100
|
+
end
|
101
|
+
|
102
|
+
# AWS S3 API returns XML when request is not valid. In this case we just
|
103
|
+
# print the returned body and exit the method.
|
104
|
+
if response.start_with?('<?xml ')
|
105
|
+
logger.error(response)
|
106
|
+
return {}
|
107
|
+
end
|
108
|
+
|
109
|
+
json = nil
|
110
|
+
begin
|
111
|
+
json = JSON.parse(response)
|
112
|
+
rescue JSON::ParserError => ex
|
113
|
+
logger.error(ex)
|
114
|
+
return {}
|
115
|
+
end
|
116
|
+
|
117
|
+
json
|
118
|
+
end
|
119
|
+
|
120
|
+
def build_config_uri
|
121
|
+
uri = URI(@data.config_route)
|
122
|
+
uri.query = QUERY_PARAMS
|
123
|
+
uri
|
124
|
+
end
|
125
|
+
|
126
|
+
def load_config
|
127
|
+
config_dir = File.dirname(CONFIG_DUMP_PATH)
|
128
|
+
Dir.mkdir(config_dir) unless File.directory?(config_dir)
|
129
|
+
|
130
|
+
return unless File.exist?(CONFIG_DUMP_PATH)
|
131
|
+
|
132
|
+
config = File.read(CONFIG_DUMP_PATH)
|
133
|
+
@data.merge!(JSON.parse(config))
|
134
|
+
end
|
135
|
+
|
136
|
+
def dump_config
|
137
|
+
config_dir = File.dirname(CONFIG_DUMP_PATH)
|
138
|
+
Dir.mkdir(config_dir) unless File.directory?(config_dir)
|
139
|
+
|
140
|
+
File.write(CONFIG_DUMP_PATH, JSON.dump(@data.to_h))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
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://v1-%<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
|
@@ -79,7 +79,7 @@ module Airbrake
|
|
79
79
|
req['Authorization'] = "Bearer #{@config.project_key}"
|
80
80
|
req['Content-Type'] = CONTENT_TYPE
|
81
81
|
req['User-Agent'] =
|
82
|
-
"#{Airbrake::
|
82
|
+
"#{Airbrake::NOTIFIER_INFO[:name]}/#{Airbrake::AIRBRAKE_RUBY_VERSION}" \
|
83
83
|
" Ruby/#{RUBY_VERSION}"
|
84
84
|
|
85
85
|
req
|
@@ -2,5 +2,15 @@
|
|
2
2
|
# More information: http://semver.org/
|
3
3
|
module Airbrake
|
4
4
|
# @return [String] the library version
|
5
|
-
|
5
|
+
# @api public
|
6
|
+
AIRBRAKE_RUBY_VERSION = '5.0.0.rc.2'.freeze
|
7
|
+
|
8
|
+
# @return [Hash{Symbol=>String}] the information about the notifier library
|
9
|
+
# @since ?.?.?
|
10
|
+
# @api public
|
11
|
+
NOTIFIER_INFO = {
|
12
|
+
name: 'airbrake-ruby'.freeze,
|
13
|
+
version: Airbrake::AIRBRAKE_RUBY_VERSION,
|
14
|
+
url: 'https://github.com/airbrake/airbrake-ruby'.freeze,
|
15
|
+
}.freeze
|
6
16
|
end
|
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
|