airbrake-ruby 2.2.3 → 2.2.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 03b38987d8ca77dcd6c8302246192bd1a9184854
4
- data.tar.gz: 93f4e52998430af6c6bcce0bca5e1b6dae3202ae
3
+ metadata.gz: 076ef6e377b4e2fbc3a5cce600393a13390da43c
4
+ data.tar.gz: 8b31cc49491997971856b5c2d1480c00881f5058
5
5
  SHA512:
6
- metadata.gz: 9f2ae08f59cb532ca0313d7d16eebab0ea9e54c5796048c57fc6418d2c7b18d37744450651f4743d4147b7288aba6f25ae0d8db88d0135104a50d35444d59be1
7
- data.tar.gz: 0a327f231fea3629de145c84570c17f48a5f0e9c37050ed0a3f53ab8cea9d59d83c17c03066c4e025051d6f0870b9b0e5591345e1d76f554a42744b0a55949b0
6
+ metadata.gz: b65105fb50ef5098b1bbfa489387e48f785fcdd8ba25dc7ac306c817385719b8fb9af684b11c447a98740dcc1a2cee17fc65376bc6934150f7f26e268c8ea88d
7
+ data.tar.gz: 8ef30b2ef9aa0a652de4c9ef1a3a0a9f8b7d60018f9441bae0e7ebe16c79fe0a793290b666132b4adf447bfcdfc4ec9c6610459471f02d9ec0d1cd5b0cabc1ad
data/lib/airbrake-ruby.rb CHANGED
@@ -15,8 +15,7 @@ require 'airbrake-ruby/response'
15
15
  require 'airbrake-ruby/nested_exception'
16
16
  require 'airbrake-ruby/notice'
17
17
  require 'airbrake-ruby/backtrace'
18
- require 'airbrake-ruby/payload_truncator'
19
- require 'airbrake-ruby/filters'
18
+ require 'airbrake-ruby/truncator'
20
19
  require 'airbrake-ruby/filters/keys_filter'
21
20
  require 'airbrake-ruby/filters/keys_whitelist'
22
21
  require 'airbrake-ruby/filters/keys_blacklist'
@@ -19,33 +19,9 @@ module Airbrake
19
19
  # @return [Integer]
20
20
  DEFAULT_WEIGHT = 0
21
21
 
22
- ##
23
- # @param [Airbrake::Config] config
24
- def initialize(config)
22
+ def initialize
25
23
  @filters = []
26
-
27
24
  DEFAULT_FILTERS.each { |f| add_filter(f.new) }
28
-
29
- if config.whitelist_keys.any?
30
- add_filter(
31
- Airbrake::Filters::KeysWhitelist.new(
32
- config.logger,
33
- config.whitelist_keys
34
- )
35
- )
36
- end
37
-
38
- if config.blacklist_keys.any?
39
- add_filter(
40
- Airbrake::Filters::KeysBlacklist.new(
41
- config.logger,
42
- config.blacklist_keys
43
- )
44
- )
45
- end
46
-
47
- return unless (root_directory = config.root_directory)
48
- add_filter(Airbrake::Filters::RootDirectoryFilter.new(root_directory))
49
25
  end
50
26
 
51
27
  ##
@@ -18,6 +18,11 @@ module Airbrake
18
18
  # which can compared with payload keys
19
19
  VALID_PATTERN_CLASSES = [String, Symbol, Regexp].freeze
20
20
 
21
+ ##
22
+ # @return [Array<Symbol>] parts of a Notice's payload that can be modified
23
+ # by blacklist/whitelist filters
24
+ FILTERABLE_KEYS = %i[environment session params].freeze
25
+
21
26
  ##
22
27
  # @return [Integer]
23
28
  attr_reader :weight
@@ -9,13 +9,16 @@ module Airbrake
9
9
  attr_reader :weight
10
10
 
11
11
  ##
12
- # @return [Array<Symbol>] the list of ignored fiber variables
13
- IGNORED_FIBER_VARIABLES = [
14
- # https://github.com/airbrake/airbrake-ruby/issues/204
15
- :__recursive_key__,
16
-
17
- # https://github.com/rails/rails/issues/28996
18
- :__rspec
12
+ # @return [Array<Class>] the list of classes that can be safely converted
13
+ # to JSON
14
+ SAFE_CLASSES = [
15
+ NilClass,
16
+ TrueClass,
17
+ FalseClass,
18
+ String,
19
+ Symbol,
20
+ Regexp,
21
+ Numeric
19
22
  ].freeze
20
23
 
21
24
  def initialize
@@ -48,16 +51,13 @@ module Airbrake
48
51
 
49
52
  def thread_variables(th)
50
53
  th.thread_variables.map.with_object({}) do |var, h|
51
- next if (value = th.thread_variable_get(var)).is_a?(IO)
52
- h[var] = value
54
+ h[var] = sanitize_value(th.thread_variable_get(var))
53
55
  end
54
56
  end
55
57
 
56
58
  def fiber_variables(th)
57
59
  th.keys.map.with_object({}) do |key, h|
58
- next if IGNORED_FIBER_VARIABLES.any? { |v| v == key }
59
- next if (value = th[key]).is_a?(IO)
60
- h[key] = value
60
+ h[key] = sanitize_value(th[key])
61
61
  end
62
62
  end
63
63
 
@@ -68,6 +68,19 @@ module Airbrake
68
68
 
69
69
  thread_info[:safe_level] = th.safe_level unless Airbrake::JRUBY
70
70
  end
71
+
72
+ def sanitize_value(value)
73
+ return value if SAFE_CLASSES.any? { |klass| value.is_a?(klass) }
74
+
75
+ case value
76
+ when Array
77
+ value = value.map { |elem| sanitize_value(elem) }
78
+ when Hash
79
+ Hash[value.map { |k, v| [k, sanitize_value(v)] }]
80
+ else
81
+ value.to_s
82
+ end
83
+ end
71
84
  end
72
85
  end
73
86
  end
@@ -4,7 +4,6 @@ module Airbrake
4
4
  # Airbrake or ignored completely.
5
5
  #
6
6
  # @since v1.0.0
7
- # rubocop:disable Metrics/ClassLength
8
7
  class Notice
9
8
  ##
10
9
  # @return [Hash{Symbol=>String}] the information about the notifier library
@@ -44,13 +43,12 @@ module Airbrake
44
43
 
45
44
  # @return [Array<Symbol>] the list of keys that can be be overwritten with
46
45
  # {Airbrake::Notice#[]=}
47
- WRITABLE_KEYS = %i[
48
- notifier
49
- context
50
- environment
51
- session
52
- params
53
- ].freeze
46
+ WRITABLE_KEYS = %i[notifier context environment session params].freeze
47
+
48
+ ##
49
+ # @return [Array<Symbol>] parts of a Notice's payload that can be modified
50
+ # by the truncator
51
+ TRUNCATABLE_KEYS = %i[errors environment session params].freeze
54
52
 
55
53
  ##
56
54
  # @return [String] the name of the host machine
@@ -77,10 +75,9 @@ module Airbrake
77
75
  params: params
78
76
  }
79
77
  @stash = {}
78
+ @truncator = Airbrake::Truncator.new(PAYLOAD_MAX_SIZE)
80
79
 
81
80
  extract_custom_attributes(exception)
82
-
83
- @truncator = PayloadTruncator.new(PAYLOAD_MAX_SIZE, @config.logger)
84
81
  end
85
82
 
86
83
  ##
@@ -99,7 +96,7 @@ module Airbrake
99
96
  return json if json && json.bytesize <= MAX_NOTICE_SIZE
100
97
  end
101
98
 
102
- break if truncate_payload.zero?
99
+ break if truncate == 0
103
100
  end
104
101
  end
105
102
 
@@ -184,17 +181,11 @@ module Airbrake
184
181
  raise Airbrake::Error, "Got #{value.class} value, wanted a Hash"
185
182
  end
186
183
 
187
- def truncate_payload
188
- @payload[:errors].each do |error|
189
- @truncator.truncate_error(error)
190
- end
191
-
192
- Filters::FILTERABLE_KEYS.each do |key|
193
- @truncator.truncate_object(@payload[key])
194
- end
184
+ def truncate
185
+ TRUNCATABLE_KEYS.each { |key| @truncator.truncate_object(self[key]) }
195
186
 
196
187
  new_max_size = @truncator.reduce_max_size
197
- if new_max_size.zero?
188
+ if new_max_size == 0
198
189
  @config.logger.error(
199
190
  "#{LOG_LABEL} truncation failed. File an issue at " \
200
191
  "https://github.com/airbrake/airbrake-ruby " \
@@ -229,5 +220,4 @@ module Airbrake
229
220
  end
230
221
  end
231
222
  end
232
- # rubocop:enable Metrics/ClassLength
233
223
  end
@@ -33,7 +33,8 @@ module Airbrake
33
33
  raise Airbrake::Error, @config.validation_error_message
34
34
  end
35
35
 
36
- @filter_chain = FilterChain.new(@config)
36
+ @filter_chain = FilterChain.new
37
+ add_default_filters
37
38
 
38
39
  @async_sender = AsyncSender.new(@config)
39
40
  @sync_sender = SyncSender.new(@config)
@@ -145,6 +146,25 @@ module Airbrake
145
146
  return caller_copy if clean_bt.empty?
146
147
  clean_bt
147
148
  end
149
+
150
+ def add_default_filters
151
+ if (whitelist_keys = @config.whitelist_keys).any?
152
+ @filter_chain.add_filter(
153
+ Airbrake::Filters::KeysWhitelist.new(@config.logger, whitelist_keys)
154
+ )
155
+ end
156
+
157
+ if (blacklist_keys = @config.blacklist_keys).any?
158
+ @filter_chain.add_filter(
159
+ Airbrake::Filters::KeysBlacklist.new(@config.logger, blacklist_keys)
160
+ )
161
+ end
162
+
163
+ return unless (root_directory = @config.root_directory)
164
+ @filter_chain.add_filter(
165
+ Airbrake::Filters::RootDirectoryFilter.new(root_directory)
166
+ )
167
+ end
148
168
  end
149
169
 
150
170
  ##
@@ -153,13 +173,13 @@ module Airbrake
153
173
  #
154
174
  # @since 2.1.0
155
175
  class NilNotifier
156
- def notify(_exception, _params = {}); end
176
+ def notify(_exception, _params = {}, &block); end
157
177
 
158
- def notify_sync(_exception, _params); end
178
+ def notify_sync(_exception, _params = {}, &block); end
159
179
 
160
180
  def add_filter(_filter = nil, &_block); end
161
181
 
162
- def build_notice(_exception, _params); end
182
+ def build_notice(_exception, _params = {}); end
163
183
 
164
184
  def close; end
165
185
 
@@ -5,7 +5,7 @@ module Airbrake
5
5
  #
6
6
  # @api private
7
7
  # @since v1.0.0
8
- class PayloadTruncator
8
+ class Truncator
9
9
  ##
10
10
  # @return [Hash] the options for +String#encode+
11
11
  ENCODING_OPTIONS = { invalid: :replace, undef: :replace }.freeze
@@ -17,29 +17,8 @@ module Airbrake
17
17
 
18
18
  ##
19
19
  # @param [Integer] max_size maximum size of hashes, arrays and strings
20
- # @param [Logger] logger the logger object
21
- def initialize(max_size, logger)
20
+ def initialize(max_size)
22
21
  @max_size = max_size
23
- @logger = logger
24
- end
25
-
26
- ##
27
- # Truncates errors (not exceptions) to fit the limit.
28
- #
29
- # @param [Hash] error
30
- # @option error [Symbol] :message
31
- # @option error [Array<String>] :backtrace
32
- # @return [void]
33
- def truncate_error(error)
34
- if error[:message].length > @max_size
35
- error[:message] = truncate_string(error[:message])
36
- @logger.info("#{LOG_LABEL} truncated the message of #{error[:type]}")
37
- end
38
-
39
- return if (dropped_frames = error[:backtrace].size - @max_size) < 0
40
-
41
- error[:backtrace] = error[:backtrace].slice(0, @max_size)
42
- @logger.info("#{LOG_LABEL} dropped #{dropped_frames} frame(s) from #{error[:type]}")
43
22
  end
44
23
 
45
24
  ##
@@ -49,26 +28,29 @@ module Airbrake
49
28
  # @param [Hash,Array] object The object to truncate
50
29
  # @param [Hash] seen The hash that helps to detect recursion
51
30
  # @return [void]
31
+ # @note This method is public to simplify testing. You probably want to use
32
+ # {truncate_notice} instead
52
33
  def truncate_object(object, seen = {})
53
34
  return seen[object] if seen[object]
54
35
 
55
36
  seen[object] = '[Circular]'.freeze
56
- truncated = if object.is_a?(Hash)
57
- truncate_hash(object, seen)
58
- elsif object.is_a?(Array)
59
- truncate_array(object, seen)
60
- elsif object.is_a?(Set)
61
- truncate_set(object, seen)
62
- else
63
- raise Airbrake::Error,
64
- "cannot truncate object: #{object} (#{object.class})"
65
- end
37
+ truncated =
38
+ if object.is_a?(Hash)
39
+ truncate_hash(object, seen)
40
+ elsif object.is_a?(Array)
41
+ truncate_array(object, seen)
42
+ elsif object.is_a?(Set)
43
+ truncate_set(object, seen)
44
+ else
45
+ raise Airbrake::Error,
46
+ "cannot truncate object: #{object} (#{object.class})"
47
+ end
66
48
  seen[object] = truncated
67
49
  end
68
50
 
69
51
  ##
70
52
  # Reduces maximum allowed size of the truncated object.
71
- # @return [void]
53
+ # @return [Integer] current +max_size+ value
72
54
  def reduce_max_size
73
55
  @max_size /= 2
74
56
  end
@@ -84,11 +66,12 @@ module Airbrake
84
66
  when Numeric, TrueClass, FalseClass, Symbol, NilClass
85
67
  val
86
68
  else
87
- stringified_val = begin
88
- val.to_json
89
- rescue *Notice::JSON_EXCEPTIONS
90
- val.to_s
91
- end
69
+ stringified_val =
70
+ begin
71
+ val.to_json
72
+ rescue *Notice::JSON_EXCEPTIONS
73
+ val.to_s
74
+ end
92
75
  truncate_string(stringified_val)
93
76
  end
94
77
  end
@@ -4,5 +4,5 @@
4
4
  module Airbrake
5
5
  ##
6
6
  # @return [String] the library version
7
- AIRBRAKE_RUBY_VERSION = '2.2.3'.freeze
7
+ AIRBRAKE_RUBY_VERSION = '2.2.4'.freeze
8
8
  end
@@ -1,222 +1,46 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Airbrake::FilterChain do
4
- before do
5
- @chain = described_class.new(config)
4
+ let(:notice) do
5
+ Airbrake::Notice.new(Airbrake::Config.new, AirbrakeTestError.new)
6
6
  end
7
7
 
8
- let(:config) { Airbrake::Config.new }
9
-
10
8
  describe "#refine" do
11
- describe "execution order" do
12
- let(:notice) do
13
- Airbrake::Notice.new(config, AirbrakeTestError.new)
14
- end
15
-
16
- it "executes keys filters last" do
17
- notice[:params] = { bingo: 'bango' }
18
- config.blacklist_keys = [:bingo]
19
- @chain = described_class.new(config)
20
-
21
- @chain.add_filter(
22
- proc do |notice|
23
- expect(notice[:params][:bingo]).to eq('bango')
24
- end
25
- )
26
-
27
- @chain.refine(notice)
28
- expect(notice[:params][:bingo]).to eq('[Filtered]')
29
- end
30
-
31
- describe "filter weight" do
32
- let(:filter) do
33
- Class.new do
34
- attr_reader :weight
9
+ let(:filter) do
10
+ Class.new do
11
+ attr_reader :weight
35
12
 
36
- def initialize(weight)
37
- @weight = weight
38
- end
39
-
40
- def call(notice)
41
- notice[:params][:bingo] << @weight
42
- end
43
- end
13
+ def initialize(weight)
14
+ @weight = weight
44
15
  end
45
16
 
46
- it "executes filters from heaviest to lightest" do
47
- notice[:params][:bingo] = []
48
-
49
- (0...3).reverse_each do |i|
50
- @chain.add_filter(filter.new(i))
51
- end
52
- @chain.refine(notice)
53
-
54
- expect(notice[:params][:bingo]).to eq([2, 1, 0])
55
- end
56
-
57
- it "stops execution once a notice was ignored" do
58
- f2 = filter.new(2)
59
- expect(f2).to receive(:call)
60
-
61
- f1 = proc { |notice| notice.ignore! }
62
-
63
- f0 = filter.new(-1)
64
- expect(f0).not_to receive(:call)
65
-
66
- [f2, f1, f0].each { |f| @chain.add_filter(f) }
67
-
68
- @chain.refine(notice)
17
+ def call(notice)
18
+ notice[:params][:bingo] << @weight
69
19
  end
70
20
  end
71
21
  end
72
22
 
73
- describe "default backtrace filters" do
74
- let(:ex) { AirbrakeTestError.new.tap { |e| e.set_backtrace(backtrace) } }
75
- let(:notice) { Airbrake::Notice.new(config, ex) }
76
-
77
- before do
78
- Gem.path << '/my/gem/root' << '/my/other/gem/root'
79
- @chain.refine(notice)
80
- @bt = notice[:errors].first[:backtrace].map { |frame| frame[:file] }
81
- end
82
-
83
- shared_examples 'root directories' do |root_directory, bt, expected_bt|
84
- let(:backtrace) { bt }
85
-
86
- before do
87
- config = Airbrake::Config.new(root_directory: root_directory)
88
- chain = described_class.new(config)
89
- chain.refine(notice)
90
- @bt = notice[:errors].first[:backtrace].map { |frame| frame[:file] }
91
- end
92
-
93
- it "filters it out" do
94
- expect(@bt).to eq(expected_bt)
95
- end
96
- end
97
-
98
- # rubocop:disable Metrics/LineLength
99
- context "gem root" do
100
- bt = [
101
- "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb:23:in `<top (required)>'",
102
- "/my/gem/root/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1327:in `load'",
103
- "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
104
- "/my/other/gem/root/gems/rspec-core-3.3.2/exe/rspec:4:in `<main>'"
105
- ]
23
+ it "executes filters from heaviest to lightest" do
24
+ notice[:params][:bingo] = []
106
25
 
107
- expected_bt = [
108
- "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb",
109
- "[GEM_ROOT]/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb",
110
- "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb",
111
- "[GEM_ROOT]/gems/rspec-core-3.3.2/exe/rspec"
112
- ]
26
+ (0...3).reverse_each { |i| subject.add_filter(filter.new(i)) }
27
+ subject.refine(notice)
113
28
 
114
- include_examples 'root directories', nil, bt, expected_bt
115
- end
116
-
117
- context "root directory" do
118
- context "when normal string path" do
119
- bt = [
120
- "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb:23:in `<top (required)>'",
121
- "/var/www/project/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1327:in `load'",
122
- "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
123
- "/var/www/project/gems/rspec-core-3.3.2/exe/rspec:4:in `<main>'"
124
- ]
125
-
126
- expected_bt = [
127
- "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb",
128
- "[PROJECT_ROOT]/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb",
129
- "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb",
130
- "[PROJECT_ROOT]/gems/rspec-core-3.3.2/exe/rspec"
131
- ]
132
-
133
- include_examples 'root directories', '/var/www/project', bt, expected_bt
134
- end
135
-
136
- context "when equals to a part of filename" do
137
- bt = [
138
- "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb:23:in `<top (required)>'",
139
- "/var/www/gems/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1327:in `load'",
140
- "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
141
- "/var/www/gems/gems/rspec-core-3.3.2/exe/rspec:4:in `<main>'"
142
- ]
143
-
144
- expected_bt = [
145
- "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb",
146
- "[PROJECT_ROOT]/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb",
147
- "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb",
148
- "[PROJECT_ROOT]/gems/rspec-core-3.3.2/exe/rspec"
149
- ]
150
-
151
- include_examples 'root directories', '/var/www/gems', bt, expected_bt
152
- end
153
-
154
- context "when normal pathname path" do
155
- bt = [
156
- "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb:23:in `<top (required)>'",
157
- "/var/www/project/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1327:in `load'",
158
- "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
159
- "/var/www/project/gems/rspec-core-3.3.2/exe/rspec:4:in `<main>'"
160
- ]
161
-
162
- expected_bt = [
163
- "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb",
164
- "[PROJECT_ROOT]/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb",
165
- "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb",
166
- "[PROJECT_ROOT]/gems/rspec-core-3.3.2/exe/rspec"
167
- ]
168
-
169
- include_examples 'root directories',
170
- Pathname.new('/var/www/project'), bt, expected_bt
171
- end
172
- end
173
- # rubocop:enable Metrics/LineLength
29
+ expect(notice[:params][:bingo]).to eq([2, 1, 0])
174
30
  end
175
31
 
176
- describe "default ignore filters" do
177
- context "system exit filter" do
178
- it "marks SystemExit exceptions as ignored" do
179
- notice = Airbrake::Notice.new(config, SystemExit.new)
180
- expect { @chain.refine(notice) }.
181
- to(change { notice.ignored? }.from(false).to(true))
182
- end
183
- end
184
-
185
- context "gem root filter" do
186
- let(:ex) do
187
- AirbrakeTestError.new.tap do |error|
188
- error.set_backtrace(['(unparseable/frame.rb:23)'])
189
- end
190
- end
191
-
192
- it "does not filter file if it is nil" do
193
- config.logger = Logger.new('/dev/null')
194
- notice = Airbrake::Notice.new(config, ex)
32
+ it "stops execution once a notice was ignored" do
33
+ f2 = filter.new(2)
34
+ expect(f2).to receive(:call)
195
35
 
196
- expect(notice[:errors].first[:file]).to be_nil
197
- expect { @chain.refine(notice) }.
198
- not_to(change { notice[:errors].first[:file] })
199
- end
200
- end
36
+ f1 = proc { |notice| notice.ignore! }
201
37
 
202
- context "root directory filter" do
203
- let(:ex) do
204
- AirbrakeTestError.new.tap do |error|
205
- error.set_backtrace(['(unparseable/frame.rb:23)'])
206
- end
207
- end
38
+ f0 = filter.new(-1)
39
+ expect(f0).not_to receive(:call)
208
40
 
209
- it "does not filter file if it is nil" do
210
- config.logger = Logger.new('/dev/null')
211
- config.root_directory = '/bingo/bango'
212
- notice = Airbrake::Notice.new(config, ex)
213
- filter_chain = described_class.new(config)
41
+ [f2, f1, f0].each { |f| subject.add_filter(f) }
214
42
 
215
- expect(notice[:errors].first[:file]).to be_nil
216
- expect { filter_chain.refine(notice) }.
217
- not_to(change { notice[:errors].first[:file] })
218
- end
219
- end
43
+ subject.refine(notice)
220
44
  end
221
45
  end
222
46
  end