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
@@ -7,18 +7,18 @@ RSpec.describe Airbrake::Filters::GemRootFilter do
7
7
  after { 2.times { Gem.path.pop } }
8
8
 
9
9
  it "replaces gem root in the backtrace with a label" do
10
- # rubocop:disable Metrics/LineLength
10
+ # rubocop:disable Layout/LineLength
11
11
  notice[:errors].first[:backtrace] = [
12
12
  { file: "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb" },
13
13
  { file: "#{root1}/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb" },
14
14
  { file: "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb" },
15
15
  { file: "#{root2}/gems/rspec-core-3.3.2/exe/rspec" },
16
16
  ]
17
- # rubocop:enable Metrics/LineLength
17
+ # rubocop:enable Layout/LineLength
18
18
 
19
19
  subject.call(notice)
20
20
 
21
- # rubocop:disable Metrics/LineLength
21
+ # rubocop:disable Layout/LineLength
22
22
  expect(notice[:errors].first[:backtrace]).to(
23
23
  eq(
24
24
  [
@@ -29,7 +29,7 @@ RSpec.describe Airbrake::Filters::GemRootFilter do
29
29
  ],
30
30
  ),
31
31
  )
32
- # rubocop:enable Metrics/LineLength
32
+ # rubocop:enable Layout/LineLength
33
33
  end
34
34
 
35
35
  it "does not filter file when it is nil" do
@@ -1,4 +1,4 @@
1
- RSpec.describe Airbrake::Filters::KeysWhitelist do
1
+ RSpec.describe Airbrake::Filters::KeysAllowlist do
2
2
  subject { described_class.new(patterns) }
3
3
 
4
4
  let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
@@ -70,10 +70,10 @@ RSpec.describe Airbrake::Filters::KeysWhitelist do
70
70
 
71
71
  it "logs an error" do
72
72
  expect(Airbrake::Loggable.instance).to receive(:error).with(
73
- /KeysWhitelist is invalid.+patterns: \[#<Object:.+>\]/,
73
+ /KeysAllowlist is invalid.+patterns: \[#<Object:.+>\]/,
74
74
  )
75
- keys_whitelist = described_class.new(patterns)
76
- keys_whitelist.call(notice)
75
+ keys_allowlist = described_class.new(patterns)
76
+ keys_allowlist.call(notice)
77
77
  end
78
78
  end
79
79
 
@@ -83,10 +83,10 @@ RSpec.describe Airbrake::Filters::KeysWhitelist do
83
83
  context "and when the filter is called once" do
84
84
  it "logs an error" do
85
85
  expect(Airbrake::Loggable.instance).to receive(:error).with(
86
- /KeysWhitelist is invalid.+patterns: \[#<Proc:.+>\]/,
86
+ /KeysAllowlist is invalid.+patterns: \[#<Proc:.+>\]/,
87
87
  )
88
- keys_whitelist = described_class.new(patterns)
89
- keys_whitelist.call(notice)
88
+ keys_allowlist = described_class.new(patterns)
89
+ keys_allowlist.call(notice)
90
90
  end
91
91
 
92
92
  include_examples(
@@ -113,10 +113,10 @@ RSpec.describe Airbrake::Filters::KeysWhitelist do
113
113
 
114
114
  it "logs an error" do
115
115
  expect(Airbrake::Loggable.instance).to receive(:error).with(
116
- /KeysWhitelist is invalid.+patterns: \[#<Object:.+>\]/,
116
+ /KeysAllowlist is invalid.+patterns: \[#<Object:.+>\]/,
117
117
  )
118
- keys_whitelist = described_class.new(patterns)
119
- keys_whitelist.call(notice)
118
+ keys_allowlist = described_class.new(patterns)
119
+ keys_allowlist.call(notice)
120
120
  end
121
121
  end
122
122
 
@@ -151,6 +151,7 @@ RSpec.describe Airbrake::Filters::KeysWhitelist do
151
151
  # thing. One is a Java exception, the other is a Ruby exception.
152
152
  # Likely a bug: https://github.com/jruby/jruby/issues/1903
153
153
  raise ex unless RUBY_ENGINE == 'jruby'
154
+
154
155
  expect { subject.call(notice) }.to raise_error(java.lang.StackOverflowError)
155
156
  end
156
157
  end
@@ -1,4 +1,4 @@
1
- RSpec.describe Airbrake::Filters::KeysBlacklist do
1
+ RSpec.describe Airbrake::Filters::KeysBlocklist do
2
2
  subject { described_class.new(patterns) }
3
3
 
4
4
  let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
@@ -91,10 +91,10 @@ RSpec.describe Airbrake::Filters::KeysBlacklist do
91
91
 
92
92
  it "logs an error" do
93
93
  expect(Airbrake::Loggable.instance).to receive(:error).with(
94
- /KeysBlacklist is invalid.+patterns: \[#<Object:.+>\]/,
94
+ /KeysBlocklist is invalid.+patterns: \[#<Object:.+>\]/,
95
95
  )
96
- keys_blacklist = described_class.new(patterns)
97
- keys_blacklist.call(notice)
96
+ keys_blocklist = described_class.new(patterns)
97
+ keys_blocklist.call(notice)
98
98
  end
99
99
  end
100
100
 
@@ -104,10 +104,10 @@ RSpec.describe Airbrake::Filters::KeysBlacklist do
104
104
  context "and when the filter is called once" do
105
105
  it "logs an error" do
106
106
  expect(Airbrake::Loggable.instance).to receive(:error).with(
107
- /KeysBlacklist is invalid.+patterns: \[#<Proc:.+>\]/,
107
+ /KeysBlocklist is invalid.+patterns: \[#<Proc:.+>\]/,
108
108
  )
109
- keys_blacklist = described_class.new(patterns)
110
- keys_blacklist.call(notice)
109
+ keys_blocklist = described_class.new(patterns)
110
+ keys_blocklist.call(notice)
111
111
  end
112
112
  end
113
113
 
@@ -133,10 +133,10 @@ RSpec.describe Airbrake::Filters::KeysBlacklist do
133
133
 
134
134
  it "logs an error" do
135
135
  expect(Airbrake::Loggable.instance).to receive(:error).with(
136
- /KeysBlacklist is invalid.+patterns: \[#<Object:.+>\]/,
136
+ /KeysBlocklist is invalid.+patterns: \[#<Object:.+>\]/,
137
137
  )
138
- keys_blacklist = described_class.new(patterns)
139
- keys_blacklist.call(notice)
138
+ keys_blocklist = described_class.new(patterns)
139
+ keys_blocklist.call(notice)
140
140
  end
141
141
  end
142
142
 
@@ -150,6 +150,16 @@ RSpec.describe Airbrake::Filters::KeysBlacklist do
150
150
  { bongo: { bish: '[Filtered]' } },
151
151
  ],
152
152
  )
153
+
154
+ it "doesn't mutate the original hash" do
155
+ params = { bongo: { bish: 'bash' } }
156
+ notice[:params] = params
157
+
158
+ blocklist = described_class.new([:bish])
159
+ blocklist.call(notice)
160
+
161
+ expect(params[:bongo][:bish]).to eq('bash')
162
+ end
153
163
  end
154
164
 
155
165
  context "and it is recursive" do
@@ -5,18 +5,18 @@ RSpec.describe Airbrake::Filters::RootDirectoryFilter do
5
5
  let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
6
6
 
7
7
  it "replaces root directory in the backtrace with a label" do
8
- # rubocop:disable Metrics/LineLength
8
+ # rubocop:disable Layout/LineLength
9
9
  notice[:errors].first[:backtrace] = [
10
10
  { file: "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb" },
11
11
  { file: "#{root_directory}/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb " },
12
12
  { file: "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb" },
13
13
  { file: "#{root_directory}/gems/rspec-core-3.3.2/exe/rspec" },
14
14
  ]
15
- # rubocop:enable Metrics/LineLength
15
+ # rubocop:enable Layout/LineLength
16
16
 
17
17
  subject.call(notice)
18
18
 
19
- # rubocop:disable Metrics/LineLength
19
+ # rubocop:disable Layout/LineLength
20
20
  expect(notice[:errors].first[:backtrace]).to(
21
21
  eq(
22
22
  [
@@ -27,7 +27,7 @@ RSpec.describe Airbrake::Filters::RootDirectoryFilter do
27
27
  ],
28
28
  ),
29
29
  )
30
- # rubocop:enable Metrics/LineLength
30
+ # rubocop:enable Layout/LineLength
31
31
  end
32
32
 
33
33
  it "does not filter file when it is nil" do
@@ -10,7 +10,7 @@ RSpec.describe Airbrake::Filters::SqlFilter do
10
10
  end
11
11
  end
12
12
 
13
- shared_examples "query blacklisting" do |query, opts|
13
+ shared_examples "query blocklisting" do |query, opts|
14
14
  it "ignores '#{query}'" do
15
15
  filter = described_class.new('postgres')
16
16
  q = Airbrake::Query.new(query: query, method: 'GET', route: '/', timing: 1)
@@ -22,7 +22,7 @@ RSpec.describe Airbrake::Filters::SqlFilter do
22
22
 
23
23
  ALL_DIALECTS = %i[mysql postgres sqlite cassandra oracle].freeze
24
24
 
25
- # rubocop:disable Metrics/LineLength
25
+ # rubocop:disable Layout/LineLength
26
26
  [
27
27
  {
28
28
  input: 'SELECT * FROM things;',
@@ -229,7 +229,7 @@ RSpec.describe Airbrake::Filters::SqlFilter do
229
229
  ].each do |test|
230
230
  include_examples 'query filtering', test
231
231
  end
232
- # rubocop:enable Metrics/LineLength
232
+ # rubocop:enable Layout/LineLength
233
233
 
234
234
  [
235
235
  'COMMIT',
@@ -263,12 +263,12 @@ RSpec.describe Airbrake::Filters::SqlFilter do
263
263
 
264
264
  'SELECT t.oid, t.typname FROM pg_type as t WHERE t.typname IN (?)',
265
265
  ].each do |query|
266
- include_examples 'query blacklisting', query, should_ignore: true
266
+ include_examples 'query blocklisting', query, should_ignore: true
267
267
  end
268
268
 
269
269
  [
270
270
  'UPDATE "users" SET "last_sign_in_at" = ? WHERE "users"."id" = ?',
271
271
  ].each do |query|
272
- include_examples 'query blacklisting', query, should_ignore: false
272
+ include_examples 'query blocklisting', query, should_ignore: false
273
273
  end
274
274
  end
@@ -149,10 +149,10 @@ RSpec.describe Airbrake::NoticeNotifier do
149
149
  expect(proxied_request.header['proxy-authorization'].first)
150
150
  .to eq('Basic dXNlcjpwYXNzd29yZA==')
151
151
 
152
- # rubocop:disable Metrics/LineLength
152
+ # rubocop:disable Layout/LineLength
153
153
  expect(proxied_request.request_line)
154
154
  .to eq("POST http://localhost:#{proxy.config[:Port]}/api/v3/projects/105138/notices HTTP/1.1\r\n")
155
- # rubocop:enable Metrics/LineLength
155
+ # rubocop:enable Layout/LineLength
156
156
  end
157
157
  end
158
158
 
@@ -234,14 +234,14 @@ RSpec.describe Airbrake::NoticeNotifier do
234
234
  end
235
235
  end
236
236
 
237
- describe ":blacklist_keys" do
237
+ describe ":blocklist_keys" do
238
238
  # Fixes https://github.com/airbrake/airbrake-ruby/issues/276
239
- context "when specified along with :whitelist_keys" do
239
+ context "when specified along with :allowlist_keys" do
240
240
  context "and when context payload is present" do
241
241
  before do
242
242
  Airbrake::Config.instance.merge(
243
- blacklist_keys: %i[password password_confirmation],
244
- whitelist_keys: [:email, /user/i, 'account_id'],
243
+ blocklist_keys: %i[password password_confirmation],
244
+ allowlist_keys: [:email, /user/i, 'account_id'],
245
245
  )
246
246
  end
247
247
 
@@ -309,7 +309,7 @@ RSpec.describe Airbrake::NoticeNotifier do
309
309
 
310
310
  notice = subject.build_notice(Exception.new)
311
311
 
312
- # rubocop:disable Metrics/LineLength
312
+ # rubocop:disable Layout/LineLength
313
313
  expect(notice[:errors].first[:backtrace]).to eq(
314
314
  [
315
315
  { file: 'org/jruby/RubyKernel.java', line: 998, function: 'eval' },
@@ -317,7 +317,7 @@ RSpec.describe Airbrake::NoticeNotifier do
317
317
  { file: '/ruby/stdlib/irb.rb:489', line: 489, function: 'block in eval_input' },
318
318
  ],
319
319
  )
320
- # rubocop:enable Metrics/LineLength
320
+ # rubocop:enable Layout/LineLength
321
321
  end
322
322
  end
323
323
 
@@ -268,7 +268,7 @@ RSpec.describe Airbrake::Notice do
268
268
  it "sets a payload value" do
269
269
  hash = { bingo: 'bango' }
270
270
  notice[:params] = hash
271
- expect(notice[:params]).to equal(hash)
271
+ expect(notice[:params]).to eq(hash)
272
272
  end
273
273
 
274
274
  it "raises error if notice is ignored" do
@@ -3,21 +3,9 @@ RSpec.describe Airbrake::PerformanceBreakdown do
3
3
  subject do
4
4
  described_class.new(
5
5
  method: 'GET', route: '/', response_type: '', groups: {},
6
- start_time: Time.now
7
6
  )
8
7
  end
9
8
 
10
9
  it { is_expected.to respond_to(:stash) }
11
10
  end
12
-
13
- describe "#end_time" do
14
- it "is always equal to start_time + 1 second by default" do
15
- time = Time.now
16
- performance_breakdown = described_class.new(
17
- method: 'GET', route: '/', response_type: '', groups: {},
18
- start_time: time
19
- )
20
- expect(performance_breakdown.end_time).to eq(time + 1)
21
- end
22
- end
23
11
  end
@@ -541,31 +541,6 @@ RSpec.describe Airbrake::PerformanceNotifier do
541
541
  end
542
542
  end
543
543
 
544
- context "when :start_time is specified (deprecated)" do
545
- before do
546
- allow(Kernel).to receive(:warn)
547
- end
548
-
549
- it "uses the value of :start_time to update stat" do
550
- subject.notify(
551
- Airbrake::Query.new(
552
- method: 'POST',
553
- route: '/foo',
554
- query: 'SELECT * FROM things',
555
- start_time: Time.new(2018, 1, 1, 0, 49, 0, 0),
556
- end_time: Time.new(2018, 1, 1, 0, 50, 0, 0),
557
- ),
558
- )
559
- subject.close
560
-
561
- expect(
562
- a_request(:put, queries).with(
563
- body: /"count":1,"sum":60000.0,"sumsq":3600000000.0/,
564
- ),
565
- ).to have_been_made
566
- end
567
- end
568
-
569
544
  context "when provided :timing is zero" do
570
545
  it "doesn't notify" do
571
546
  queue = Airbrake::Queue.new(queue: 'bananas', error_count: 0, timing: 0)
@@ -2,20 +2,10 @@ RSpec.describe Airbrake::Query do
2
2
  describe "#stash" do
3
3
  subject do
4
4
  described_class.new(
5
- method: 'GET', route: '/', query: '', start_time: Time.now,
5
+ method: 'GET', route: '/', query: '',
6
6
  )
7
7
  end
8
8
 
9
9
  it { is_expected.to respond_to(:stash) }
10
10
  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
- query = described_class.new(
16
- method: 'GET', route: '/', query: '', start_time: time,
17
- )
18
- expect(query.end_time).to eq(time + 1)
19
- end
20
- end
21
11
  end
@@ -9,21 +9,9 @@ RSpec.describe Airbrake::Queue do
9
9
  it { is_expected.to respond_to(:stash) }
10
10
  end
11
11
 
12
- describe "#end_time" do
13
- it "is always equal to start_time + 1 second by default" do
14
- time = Time.now
15
- queue = described_class.new(
16
- queue: 'bananas', error_count: 0, start_time: time,
17
- )
18
- expect(queue.end_time).to eq(time + 1)
19
- end
20
- end
21
-
22
12
  describe "#route" do
23
13
  it "always returns an empty route" do
24
- queue = described_class.new(
25
- queue: 'a', error_count: 0, start_time: Time.now,
26
- )
14
+ queue = described_class.new(queue: 'a', error_count: 0)
27
15
  expect(queue.route).to be_empty
28
16
  end
29
17
  end
@@ -0,0 +1,365 @@
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
+ settings_data.merge!('poll_sec' => 123, 'config_route' => 'abc')
13
+
14
+ expect(settings_data.interval).to eq(123)
15
+ expect(settings_data.config_route(''))
16
+ .to eq('abc/2020-06-18/config/123/config.json')
17
+ end
18
+ end
19
+
20
+ describe "#interval" do
21
+ context "when given data has zero interval" do
22
+ let(:data) do
23
+ { 'poll_sec' => 0 }
24
+ end
25
+
26
+ it "returns the default interval" do
27
+ expect(described_class.new(project_id, data).interval).to eq(600)
28
+ end
29
+ end
30
+
31
+ context "when given data has negative interval" do
32
+ let(:data) do
33
+ { 'poll_sec' => -1 }
34
+ end
35
+
36
+ it "returns the default interval" do
37
+ expect(described_class.new(project_id, data).interval).to eq(600)
38
+ end
39
+ end
40
+
41
+ context "when given data has nil interval" do
42
+ let(:data) do
43
+ { 'poll_sec' => nil }
44
+ end
45
+
46
+ it "returns the default interval" do
47
+ expect(described_class.new(project_id, data).interval).to eq(600)
48
+ end
49
+ end
50
+
51
+ context "when given data has a positive interval" do
52
+ let(:data) do
53
+ { 'poll_sec' => 123 }
54
+ end
55
+
56
+ it "returns the interval from data" do
57
+ expect(described_class.new(project_id, data).interval).to eq(123)
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "#config_route" do
63
+ let(:host) { 'https://v1-production-notifier-configs.s3.amazonaws.com' }
64
+
65
+ context "when given a remote host through the remote config" do
66
+ context "and when the remote host ends with a slash" do
67
+ let(:data) do
68
+ { 'config_route' => 'http://example.com/' }
69
+ end
70
+
71
+ it "returns the route with the host" do
72
+ expect(described_class.new(project_id, data).config_route(host)).to eq(
73
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
74
+ )
75
+ end
76
+ end
77
+
78
+ context "and when the remote host doesn't with a slash" do
79
+ let(:data) do
80
+ { 'config_route' => 'http://example.com' }
81
+ end
82
+
83
+ it "returns the route with the host" do
84
+ expect(described_class.new(project_id, data).config_route(host)).to eq(
85
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
86
+ )
87
+ end
88
+ end
89
+ end
90
+
91
+ context "when given a remote host through local configuration" do
92
+ context "and when the remote host ends with a slash" do
93
+ let(:host) { 'http://example.com/' }
94
+
95
+ it "returns the route with the host" do
96
+ expect(described_class.new(project_id, {}).config_route(host)).to eq(
97
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
98
+ )
99
+ end
100
+ end
101
+
102
+ context "and when the remote host doesn't with a slash" do
103
+ let(:host) { 'http://example.com' }
104
+
105
+ it "returns the route with the host" do
106
+ expect(described_class.new(project_id, {}).config_route(host)).to eq(
107
+ "http://example.com/2020-06-18/config/#{project_id}/config.json",
108
+ )
109
+ end
110
+ end
111
+ end
112
+
113
+ context "when the given remote host in the remote config is nil" do
114
+ let(:data) do
115
+ { 'config_route' => nil }
116
+ end
117
+
118
+ it "returns the route with the given host instead" do
119
+ expect(described_class.new(project_id, data).config_route(host)).to eq(
120
+ 'https://v1-production-notifier-configs.s3.amazonaws.com/' \
121
+ "2020-06-18/config/#{project_id}/config.json",
122
+ )
123
+ end
124
+ end
125
+ end
126
+
127
+ describe "#error_notifications?" do
128
+ context "when the 'errors' setting is present" do
129
+ context "and when it is enabled" do
130
+ let(:data) do
131
+ {
132
+ 'settings' => [
133
+ {
134
+ 'name' => 'errors',
135
+ 'enabled' => true,
136
+ },
137
+ ],
138
+ }
139
+ end
140
+
141
+ it "returns true" do
142
+ expect(described_class.new(project_id, data).error_notifications?)
143
+ .to eq(true)
144
+ end
145
+ end
146
+
147
+ context "and when it is disabled" do
148
+ let(:data) do
149
+ {
150
+ 'settings' => [
151
+ {
152
+ 'name' => 'errors',
153
+ 'enabled' => false,
154
+ },
155
+ ],
156
+ }
157
+ end
158
+
159
+ it "returns false" do
160
+ expect(described_class.new(project_id, data).error_notifications?)
161
+ .to eq(false)
162
+ end
163
+ end
164
+ end
165
+
166
+ context "when the 'errors' setting is missing" do
167
+ let(:data) do
168
+ { 'settings' => [] }
169
+ end
170
+
171
+ it "returns true" do
172
+ expect(described_class.new(project_id, data).error_notifications?)
173
+ .to eq(true)
174
+ end
175
+ end
176
+ end
177
+
178
+ describe "#performance_stats?" do
179
+ context "when the 'apm' setting is present" do
180
+ context "and when it is enabled" do
181
+ let(:data) do
182
+ {
183
+ 'settings' => [
184
+ {
185
+ 'name' => 'apm',
186
+ 'enabled' => true,
187
+ },
188
+ ],
189
+ }
190
+ end
191
+
192
+ it "returns true" do
193
+ expect(described_class.new(project_id, data).performance_stats?)
194
+ .to eq(true)
195
+ end
196
+ end
197
+
198
+ context "and when it is disabled" do
199
+ let(:data) do
200
+ {
201
+ 'settings' => [
202
+ {
203
+ 'name' => 'apm',
204
+ 'enabled' => false,
205
+ },
206
+ ],
207
+ }
208
+ end
209
+
210
+ it "returns false" do
211
+ expect(described_class.new(project_id, data).performance_stats?)
212
+ .to eq(false)
213
+ end
214
+ end
215
+ end
216
+
217
+ context "when the 'apm' setting is missing" do
218
+ let(:data) do
219
+ { 'settings' => [] }
220
+ end
221
+
222
+ it "returns true" do
223
+ expect(described_class.new(project_id, data).performance_stats?)
224
+ .to eq(true)
225
+ end
226
+ end
227
+ end
228
+
229
+ describe "#error_host" do
230
+ context "when the 'errors' setting is present" do
231
+ context "and when 'endpoint' is specified" do
232
+ let(:endpoint) { 'https://api.example.com/' }
233
+
234
+ let(:data) do
235
+ {
236
+ 'settings' => [
237
+ {
238
+ 'name' => 'errors',
239
+ 'enabled' => true,
240
+ 'endpoint' => endpoint,
241
+ },
242
+ ],
243
+ }
244
+ end
245
+
246
+ it "returns the endpoint" do
247
+ expect(described_class.new(project_id, data).error_host).to eq(endpoint)
248
+ end
249
+ end
250
+
251
+ context "and when an endpoint is NOT specified" do
252
+ let(:data) do
253
+ {
254
+ 'settings' => [
255
+ {
256
+ 'name' => 'errors',
257
+ 'enabled' => true,
258
+ },
259
+ ],
260
+ }
261
+ end
262
+
263
+ it "returns nil" do
264
+ expect(described_class.new(project_id, data).error_host).to be_nil
265
+ end
266
+ end
267
+ end
268
+
269
+ context "when the 'errors' setting is missing" do
270
+ let(:data) do
271
+ { 'settings' => [] }
272
+ end
273
+
274
+ it "returns nil" do
275
+ expect(described_class.new(project_id, data).error_host).to be_nil
276
+ end
277
+ end
278
+ end
279
+
280
+ describe "#apm_host" do
281
+ context "when the 'apm' setting is present" do
282
+ context "and when 'endpoint' is specified" do
283
+ let(:endpoint) { 'https://api.example.com/' }
284
+
285
+ let(:data) do
286
+ {
287
+ 'settings' => [
288
+ {
289
+ 'name' => 'apm',
290
+ 'enabled' => true,
291
+ 'endpoint' => endpoint,
292
+ },
293
+ ],
294
+ }
295
+ end
296
+
297
+ it "returns the endpoint" do
298
+ expect(described_class.new(project_id, data).apm_host).to eq(endpoint)
299
+ end
300
+ end
301
+
302
+ context "and when an endpoint is NOT specified" do
303
+ let(:data) do
304
+ {
305
+ 'settings' => [
306
+ {
307
+ 'name' => 'apm',
308
+ 'enabled' => true,
309
+ },
310
+ ],
311
+ }
312
+ end
313
+
314
+ it "returns nil" do
315
+ expect(described_class.new(project_id, data).apm_host).to be_nil
316
+ end
317
+ end
318
+ end
319
+
320
+ context "when the 'apm' setting is missing" do
321
+ let(:data) do
322
+ { 'settings' => [] }
323
+ end
324
+
325
+ it "returns nil" do
326
+ expect(described_class.new(project_id, data).apm_host).to be_nil
327
+ end
328
+ end
329
+ end
330
+
331
+ describe "#to_h" do
332
+ let(:data) do
333
+ {
334
+ 'poll_sec' => 123,
335
+ 'settings' => [
336
+ {
337
+ 'name' => 'apm',
338
+ 'enabled' => false,
339
+ },
340
+ ],
341
+ }
342
+ end
343
+
344
+ subject { described_class.new(project_id, data) }
345
+
346
+ it "returns a hash representation of settings" do
347
+ expect(described_class.new(project_id, data).to_h).to eq(data)
348
+ end
349
+
350
+ it "doesn't allow mutation of the original data object" do
351
+ hash = subject.to_h
352
+ hash['poll_sec'] = 0
353
+
354
+ expect(subject.to_h).to eq(
355
+ 'poll_sec' => 123,
356
+ 'settings' => [
357
+ {
358
+ 'name' => 'apm',
359
+ 'enabled' => false,
360
+ },
361
+ ],
362
+ )
363
+ end
364
+ end
365
+ end