airbrake-ruby 4.14.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +23 -35
  3. data/lib/airbrake-ruby/async_sender.rb +1 -1
  4. data/lib/airbrake-ruby/backtrace.rb +6 -5
  5. data/lib/airbrake-ruby/config.rb +37 -13
  6. data/lib/airbrake-ruby/config/processor.rb +84 -0
  7. data/lib/airbrake-ruby/config/validator.rb +6 -0
  8. data/lib/airbrake-ruby/file_cache.rb +1 -1
  9. data/lib/airbrake-ruby/filter_chain.rb +1 -0
  10. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  11. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  12. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +1 -2
  13. data/lib/airbrake-ruby/filters/git_repository_filter.rb +3 -0
  14. data/lib/airbrake-ruby/filters/git_revision_filter.rb +2 -0
  15. data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
  16. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  17. data/lib/airbrake-ruby/filters/keys_filter.rb +26 -18
  18. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  19. data/lib/airbrake-ruby/filters/sql_filter.rb +4 -4
  20. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  21. data/lib/airbrake-ruby/filters/thread_filter.rb +2 -0
  22. data/lib/airbrake-ruby/ignorable.rb +1 -0
  23. data/lib/airbrake-ruby/notice.rb +1 -8
  24. data/lib/airbrake-ruby/notice_notifier.rb +1 -0
  25. data/lib/airbrake-ruby/performance_breakdown.rb +1 -6
  26. data/lib/airbrake-ruby/performance_notifier.rb +2 -15
  27. data/lib/airbrake-ruby/promise.rb +1 -0
  28. data/lib/airbrake-ruby/query.rb +1 -6
  29. data/lib/airbrake-ruby/queue.rb +1 -8
  30. data/lib/airbrake-ruby/remote_settings.rb +145 -0
  31. data/lib/airbrake-ruby/remote_settings/settings_data.rb +120 -0
  32. data/lib/airbrake-ruby/request.rb +1 -8
  33. data/lib/airbrake-ruby/stat.rb +1 -12
  34. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  35. data/lib/airbrake-ruby/tdigest.rb +2 -0
  36. data/lib/airbrake-ruby/thread_pool.rb +2 -0
  37. data/lib/airbrake-ruby/truncator.rb +8 -2
  38. data/lib/airbrake-ruby/version.rb +11 -1
  39. data/spec/airbrake_spec.rb +59 -36
  40. data/spec/backtrace_spec.rb +26 -26
  41. data/spec/code_hunk_spec.rb +2 -2
  42. data/spec/config/processor_spec.rb +209 -0
  43. data/spec/config/validator_spec.rb +18 -1
  44. data/spec/config_spec.rb +13 -6
  45. data/spec/filters/gem_root_filter_spec.rb +4 -4
  46. data/spec/filters/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +11 -10
  47. data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +20 -10
  48. data/spec/filters/root_directory_filter_spec.rb +4 -4
  49. data/spec/filters/sql_filter_spec.rb +5 -5
  50. data/spec/notice_notifier/options_spec.rb +6 -6
  51. data/spec/notice_notifier_spec.rb +2 -2
  52. data/spec/notice_spec.rb +1 -1
  53. data/spec/performance_breakdown_spec.rb +0 -12
  54. data/spec/performance_notifier_spec.rb +0 -25
  55. data/spec/query_spec.rb +1 -11
  56. data/spec/queue_spec.rb +1 -13
  57. data/spec/remote_settings/settings_data_spec.rb +365 -0
  58. data/spec/remote_settings_spec.rb +230 -0
  59. data/spec/request_spec.rb +1 -13
  60. data/spec/spec_helper.rb +4 -4
  61. data/spec/stat_spec.rb +0 -9
  62. data/spec/sync_sender_spec.rb +3 -1
  63. data/spec/thread_pool_spec.rb +25 -5
  64. metadata +21 -12
@@ -0,0 +1,230 @@
1
+ RSpec.describe Airbrake::RemoteSettings do
2
+ let(:project_id) { 123 }
3
+ let(:host) { 'https://v1-production-notifier-configs.s3.amazonaws.com' }
4
+
5
+ let(:endpoint) do
6
+ "#{host}/2020-06-18/config/#{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
+ let!(:stub) do
29
+ stub_request(:get, Regexp.new(endpoint))
30
+ .to_return(status: 200, body: body.to_json)
31
+ end
32
+
33
+ before do
34
+ # Do not create config dumps on disk.
35
+ allow(Dir).to receive(:mkdir).with(config_dir)
36
+ allow(File).to receive(:write).with(config_path, anything)
37
+ end
38
+
39
+ describe ".poll" do
40
+ describe "config loading" do
41
+ let(:settings_data) { described_class::SettingsData.new(project_id, body) }
42
+
43
+ before do
44
+ allow(File).to receive(:exist?).with(config_path).and_return(true)
45
+ allow(File).to receive(:read).with(config_path).and_return(body.to_json)
46
+
47
+ allow(described_class::SettingsData).to receive(:new).and_return(settings_data)
48
+ end
49
+
50
+ it "loads the config from disk" do
51
+ expect(File).to receive(:read).with(config_path)
52
+ expect(settings_data).to receive(:merge!).with(body).twice
53
+
54
+ remote_settings = described_class.poll(project_id, host) {}
55
+ sleep(0.2)
56
+ remote_settings.stop_polling
57
+
58
+ expect(stub).to have_been_requested.once
59
+ end
60
+
61
+ it "yields the config to the block twice" do
62
+ block = proc {}
63
+ expect(block).to receive(:call).twice
64
+
65
+ remote_settings = described_class.poll(project_id, host, &block)
66
+ sleep(0.2)
67
+ remote_settings.stop_polling
68
+
69
+ expect(stub).to have_been_requested.once
70
+ end
71
+
72
+ context "when config loading fails" do
73
+ it "logs an error" do
74
+ expect(File).to receive(:read).and_raise(StandardError)
75
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
76
+ '**Airbrake: config loading failed: StandardError',
77
+ )
78
+
79
+ remote_settings = described_class.poll(project_id, host) {}
80
+ sleep(0.2)
81
+ remote_settings.stop_polling
82
+
83
+ expect(stub).to have_been_requested.once
84
+ end
85
+ end
86
+ end
87
+
88
+ context "when no errors are raised" do
89
+ it "makes a request to AWS S3" do
90
+ remote_settings = described_class.poll(project_id, host) {}
91
+ sleep(0.1)
92
+ remote_settings.stop_polling
93
+
94
+ expect(stub).to have_been_requested.at_least_once
95
+ end
96
+
97
+ it "sends params about the environment with the request" do
98
+ remote_settings = described_class.poll(project_id, host) {}
99
+ sleep(0.1)
100
+ remote_settings.stop_polling
101
+
102
+ stub_with_query_params = stub.with(
103
+ query: URI.decode_www_form(described_class::QUERY_PARAMS).to_h,
104
+ )
105
+ expect(stub_with_query_params).to have_been_requested.at_least_once
106
+ end
107
+
108
+ it "fetches remote settings" do
109
+ settings = nil
110
+ remote_settings = described_class.poll(project_id, host) do |data|
111
+ settings = data
112
+ end
113
+ sleep(0.1)
114
+ remote_settings.stop_polling
115
+
116
+ expect(settings.error_notifications?).to eq(true)
117
+ expect(settings.performance_stats?).to eq(false)
118
+ expect(settings.interval).to eq(1)
119
+ end
120
+ end
121
+
122
+ context "when an error is raised while making a HTTP request" do
123
+ before do
124
+ allow(Net::HTTP).to receive(:get).and_raise(StandardError)
125
+ end
126
+
127
+ it "doesn't fetch remote settings" do
128
+ settings = nil
129
+ remote_settings = described_class.poll(project_id, host) do |data|
130
+ settings = data
131
+ end
132
+ sleep(0.1)
133
+ remote_settings.stop_polling
134
+
135
+ expect(stub).not_to have_been_requested
136
+ expect(settings.interval).to eq(600)
137
+ end
138
+ end
139
+
140
+ context "when an error is raised while parsing returned JSON" do
141
+ before do
142
+ allow(JSON).to receive(:parse).and_raise(JSON::ParserError)
143
+ end
144
+
145
+ it "doesn't update settings data" do
146
+ settings = nil
147
+ remote_settings = described_class.poll(project_id, host) do |data|
148
+ settings = data
149
+ end
150
+ sleep(0.1)
151
+ remote_settings.stop_polling
152
+
153
+ expect(stub).to have_been_requested.once
154
+ expect(settings.interval).to eq(600)
155
+ end
156
+ end
157
+
158
+ context "when API returns an XML response" do
159
+ let!(:stub) do
160
+ stub_request(:get, Regexp.new(endpoint))
161
+ .to_return(status: 200, body: '<?xml ...')
162
+ end
163
+
164
+ it "doesn't update settings data" do
165
+ settings = nil
166
+ remote_settings = described_class.poll(project_id, host) do |data|
167
+ settings = data
168
+ end
169
+ sleep(0.1)
170
+ remote_settings.stop_polling
171
+
172
+ expect(stub).to have_been_requested.once
173
+ expect(settings.interval).to eq(600)
174
+ end
175
+ end
176
+
177
+ context "when a config route is specified in the returned data" do
178
+ let(:new_endpoint) do
179
+ "http://example.com"
180
+ end
181
+
182
+ let(:body) do
183
+ { 'config_route' => new_endpoint, 'poll_sec' => 0.1 }
184
+ end
185
+
186
+ let!(:new_stub) do
187
+ stub_request(:get, Regexp.new(new_endpoint))
188
+ .to_return(status: 200, body: body.to_json)
189
+ end
190
+
191
+ it "makes the next request to the specified config route" do
192
+ remote_settings = described_class.poll(project_id, host) {}
193
+ sleep(0.2)
194
+
195
+ remote_settings.stop_polling
196
+
197
+ expect(stub).to have_been_requested.once
198
+ expect(new_stub).to have_been_requested.once
199
+ end
200
+ end
201
+ end
202
+
203
+ describe "#stop_polling" do
204
+ it "dumps config data to disk" do
205
+ expect(Dir).to receive(:mkdir).with(config_dir)
206
+ expect(File).to receive(:write).with(config_path, body.to_json)
207
+
208
+ remote_settings = described_class.poll(project_id, host) {}
209
+ sleep(0.2)
210
+ remote_settings.stop_polling
211
+
212
+ expect(stub).to have_been_requested.once
213
+ end
214
+
215
+ context "when config dumping fails" do
216
+ it "logs an error" do
217
+ expect(File).to receive(:write).and_raise(StandardError)
218
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
219
+ '**Airbrake: config dumping failed: StandardError',
220
+ )
221
+
222
+ remote_settings = described_class.poll(project_id, host) {}
223
+ sleep(0.2)
224
+ remote_settings.stop_polling
225
+
226
+ expect(stub).to have_been_requested.once
227
+ end
228
+ end
229
+ end
230
+ end
@@ -1,21 +1,9 @@
1
1
  RSpec.describe Airbrake::Request do
2
2
  describe "#stash" do
3
3
  subject do
4
- described_class.new(
5
- method: 'GET', route: '/', status_code: 200, start_time: Time.now,
6
- )
4
+ described_class.new(method: 'GET', route: '/', status_code: 200)
7
5
  end
8
6
 
9
7
  it { is_expected.to respond_to(:stash) }
10
8
  end
11
-
12
- describe "#end_time" do
13
- it "is always equal to start_time + 1 second by default" do
14
- time = Time.now
15
- request = described_class.new(
16
- method: 'GET', route: '/', status_code: 200, start_time: time,
17
- )
18
- expect(request.end_time).to eq(time + 1)
19
- end
20
- end
21
9
  end
@@ -33,7 +33,7 @@ class AirbrakeTestError < RuntimeError
33
33
 
34
34
  def initialize(*)
35
35
  super
36
- # rubocop:disable Metrics/LineLength
36
+ # rubocop:disable Layout/LineLength
37
37
  @backtrace = [
38
38
  "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb:23:in `<top (required)>'",
39
39
  "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
@@ -49,7 +49,7 @@ class AirbrakeTestError < RuntimeError
49
49
  "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:41:in `invoke'",
50
50
  "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/exe/rspec:4:in `<main>'",
51
51
  ]
52
- # rubocop:enable Metrics/LineLength
52
+ # rubocop:enable Layout/LineLength
53
53
  end
54
54
 
55
55
  # rubocop:disable Naming/AccessorMethodName
@@ -66,7 +66,7 @@ end
66
66
  class JavaAirbrakeTestError < AirbrakeTestError
67
67
  def initialize(*)
68
68
  super
69
- # rubocop:disable Metrics/LineLength
69
+ # rubocop:disable Layout/LineLength
70
70
  @backtrace = [
71
71
  "org.jruby.java.invokers.InstanceMethodInvoker.call(InstanceMethodInvoker.java:26)",
72
72
  "org.jruby.ir.interpreter.Interpreter.INTERPRET_EVAL(Interpreter.java:126)",
@@ -80,7 +80,7 @@ class JavaAirbrakeTestError < AirbrakeTestError
80
80
  "org.jruby.Main.run(Main.java:225)",
81
81
  "org.jruby.Main.main(Main.java:197)",
82
82
  ]
83
- # rubocop:enable Metrics/LineLength
83
+ # rubocop:enable Layout/LineLength
84
84
  end
85
85
 
86
86
  def is_a?(*)
@@ -10,15 +10,6 @@ RSpec.describe Airbrake::Stat do
10
10
  end
11
11
  end
12
12
 
13
- describe "#increment" do
14
- let(:start_time) { Time.new(2018, 1, 1, 0, 0, 20, 0) }
15
- let(:end_time) { Time.new(2018, 1, 1, 0, 0, 22, 0) }
16
-
17
- before { subject.increment(start_time, end_time) }
18
-
19
- its(:sum) { is_expected.to eq(2000) }
20
- end
21
-
22
13
  describe "#increment_ms" do
23
14
  before { subject.increment_ms(1000) }
24
15
 
@@ -27,7 +27,9 @@ RSpec.describe Airbrake::SyncSender do
27
27
  expect(
28
28
  a_request(:post, endpoint).with(
29
29
  headers: {
30
- 'User-Agent' => %r{airbrake-ruby/\d+\.\d+\.\d+ Ruby/\d+\.\d+\.\d+},
30
+ 'User-Agent' => %r{
31
+ airbrake-ruby/\d+\.\d+\.\d+(\.rc\.\d+)?\sRuby/\d+\.\d+\.\d+
32
+ }x,
31
33
  },
32
34
  ),
33
35
  ).to have_been_made.once
@@ -85,11 +85,31 @@ RSpec.describe Airbrake::ThreadPool do
85
85
  expect(subject).not_to have_workers
86
86
  end
87
87
 
88
- it "respawns workers on fork()", skip: %w[jruby].include?(RUBY_ENGINE) do
89
- pid = fork { expect(subject).to have_workers }
90
- Process.wait(pid)
91
- subject.close
92
- expect(subject).not_to have_workers
88
+ describe "forking behavior" do
89
+ before do
90
+ skip('fork() is unsupported on JRuby') if %w[jruby].include?(RUBY_ENGINE)
91
+ unless Process.respond_to?(:last_status)
92
+ skip('Process.last_status is unsupported on this Ruby')
93
+ end
94
+ end
95
+
96
+ it "respawns workers on fork()" do
97
+ pid = fork { expect(subject).to have_workers }
98
+ Process.wait(pid)
99
+ subject.close
100
+
101
+ expect(Process.last_status).to be_success
102
+ expect(subject).not_to have_workers
103
+ end
104
+
105
+ it "ensures that a new thread group is created per process" do
106
+ subject << 1
107
+ pid = fork { subject.has_workers? }
108
+ Process.wait(pid)
109
+ subject.close
110
+
111
+ expect(Process.last_status).to be_success
112
+ end
93
113
  end
94
114
  end
95
115
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airbrake-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.14.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Airbrake Technologies, Inc.
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-10 00:00:00.000000000 Z
11
+ date: 2020-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbtree3
@@ -44,6 +44,7 @@ files:
44
44
  - lib/airbrake-ruby/benchmark.rb
45
45
  - lib/airbrake-ruby/code_hunk.rb
46
46
  - lib/airbrake-ruby/config.rb
47
+ - lib/airbrake-ruby/config/processor.rb
47
48
  - lib/airbrake-ruby/config/validator.rb
48
49
  - lib/airbrake-ruby/deploy_notifier.rb
49
50
  - lib/airbrake-ruby/file_cache.rb
@@ -55,9 +56,9 @@ files:
55
56
  - lib/airbrake-ruby/filters/git_last_checkout_filter.rb
56
57
  - lib/airbrake-ruby/filters/git_repository_filter.rb
57
58
  - lib/airbrake-ruby/filters/git_revision_filter.rb
58
- - lib/airbrake-ruby/filters/keys_blacklist.rb
59
+ - lib/airbrake-ruby/filters/keys_allowlist.rb
60
+ - lib/airbrake-ruby/filters/keys_blocklist.rb
59
61
  - lib/airbrake-ruby/filters/keys_filter.rb
60
- - lib/airbrake-ruby/filters/keys_whitelist.rb
61
62
  - lib/airbrake-ruby/filters/root_directory_filter.rb
62
63
  - lib/airbrake-ruby/filters/sql_filter.rb
63
64
  - lib/airbrake-ruby/filters/system_exit_filter.rb
@@ -77,6 +78,8 @@ files:
77
78
  - lib/airbrake-ruby/promise.rb
78
79
  - lib/airbrake-ruby/query.rb
79
80
  - lib/airbrake-ruby/queue.rb
81
+ - lib/airbrake-ruby/remote_settings.rb
82
+ - lib/airbrake-ruby/remote_settings/settings_data.rb
80
83
  - lib/airbrake-ruby/request.rb
81
84
  - lib/airbrake-ruby/response.rb
82
85
  - lib/airbrake-ruby/stashable.rb
@@ -93,6 +96,7 @@ files:
93
96
  - spec/backtrace_spec.rb
94
97
  - spec/benchmark_spec.rb
95
98
  - spec/code_hunk_spec.rb
99
+ - spec/config/processor_spec.rb
96
100
  - spec/config/validator_spec.rb
97
101
  - spec/config_spec.rb
98
102
  - spec/deploy_notifier_spec.rb
@@ -105,8 +109,8 @@ files:
105
109
  - spec/filters/git_last_checkout_filter_spec.rb
106
110
  - spec/filters/git_repository_filter.rb
107
111
  - spec/filters/git_revision_filter_spec.rb
108
- - spec/filters/keys_blacklist_spec.rb
109
- - spec/filters/keys_whitelist_spec.rb
112
+ - spec/filters/keys_allowlist_spec.rb
113
+ - spec/filters/keys_blocklist_spec.rb
110
114
  - spec/filters/root_directory_filter_spec.rb
111
115
  - spec/filters/sql_filter_spec.rb
112
116
  - spec/filters/system_exit_filter_spec.rb
@@ -131,6 +135,8 @@ files:
131
135
  - spec/promise_spec.rb
132
136
  - spec/query_spec.rb
133
137
  - spec/queue_spec.rb
138
+ - spec/remote_settings/settings_data_spec.rb
139
+ - spec/remote_settings_spec.rb
134
140
  - spec/request_spec.rb
135
141
  - spec/response_spec.rb
136
142
  - spec/spec_helper.rb
@@ -146,7 +152,7 @@ homepage: https://airbrake.io
146
152
  licenses:
147
153
  - MIT
148
154
  metadata: {}
149
- post_install_message:
155
+ post_install_message:
150
156
  rdoc_options: []
151
157
  require_paths:
152
158
  - lib
@@ -154,7 +160,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
154
160
  requirements:
155
161
  - - ">="
156
162
  - !ruby/object:Gem::Version
157
- version: '2.1'
163
+ version: '2.3'
158
164
  required_rubygems_version: !ruby/object:Gem::Requirement
159
165
  requirements:
160
166
  - - ">="
@@ -162,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
162
168
  version: '0'
163
169
  requirements: []
164
170
  rubygems_version: 3.1.2
165
- signing_key:
171
+ signing_key:
166
172
  specification_version: 4
167
173
  summary: Ruby notifier for https://airbrake.io
168
174
  test_files:
@@ -172,14 +178,14 @@ test_files:
172
178
  - spec/filters/exception_attributes_filter_spec.rb
173
179
  - spec/filters/root_directory_filter_spec.rb
174
180
  - spec/filters/sql_filter_spec.rb
175
- - spec/filters/keys_whitelist_spec.rb
176
181
  - spec/filters/system_exit_filter_spec.rb
177
182
  - spec/filters/thread_filter_spec.rb
183
+ - spec/filters/keys_allowlist_spec.rb
178
184
  - spec/filters/dependency_filter_spec.rb
179
185
  - spec/filters/context_filter_spec.rb
180
186
  - spec/filters/git_last_checkout_filter_spec.rb
181
187
  - spec/filters/git_revision_filter_spec.rb
182
- - spec/filters/keys_blacklist_spec.rb
188
+ - spec/filters/keys_blocklist_spec.rb
183
189
  - spec/filters/gem_root_filter_spec.rb
184
190
  - spec/filters/git_repository_filter.rb
185
191
  - spec/spec_helper.rb
@@ -196,6 +202,7 @@ test_files:
196
202
  - spec/promise_spec.rb
197
203
  - spec/thread_pool_spec.rb
198
204
  - spec/config/validator_spec.rb
205
+ - spec/config/processor_spec.rb
199
206
  - spec/sync_sender_spec.rb
200
207
  - spec/ignorable_spec.rb
201
208
  - spec/deploy_notifier_spec.rb
@@ -207,6 +214,7 @@ test_files:
207
214
  - spec/request_spec.rb
208
215
  - spec/notice_notifier/options_spec.rb
209
216
  - spec/filter_chain_spec.rb
217
+ - spec/remote_settings/settings_data_spec.rb
210
218
  - spec/response_spec.rb
211
219
  - spec/queue_spec.rb
212
220
  - spec/code_hunk_spec.rb
@@ -220,3 +228,4 @@ test_files:
220
228
  - spec/inspectable_spec.rb
221
229
  - spec/stashable_spec.rb
222
230
  - spec/query_spec.rb
231
+ - spec/remote_settings_spec.rb