airbrake-ruby 4.6.0

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.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/lib/airbrake-ruby.rb +513 -0
  3. data/lib/airbrake-ruby/async_sender.rb +142 -0
  4. data/lib/airbrake-ruby/backtrace.rb +196 -0
  5. data/lib/airbrake-ruby/benchmark.rb +39 -0
  6. data/lib/airbrake-ruby/code_hunk.rb +51 -0
  7. data/lib/airbrake-ruby/config.rb +229 -0
  8. data/lib/airbrake-ruby/config/validator.rb +91 -0
  9. data/lib/airbrake-ruby/deploy_notifier.rb +36 -0
  10. data/lib/airbrake-ruby/file_cache.rb +48 -0
  11. data/lib/airbrake-ruby/filter_chain.rb +95 -0
  12. data/lib/airbrake-ruby/filters/context_filter.rb +29 -0
  13. data/lib/airbrake-ruby/filters/dependency_filter.rb +31 -0
  14. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +46 -0
  15. data/lib/airbrake-ruby/filters/gem_root_filter.rb +33 -0
  16. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +92 -0
  17. data/lib/airbrake-ruby/filters/git_repository_filter.rb +64 -0
  18. data/lib/airbrake-ruby/filters/git_revision_filter.rb +66 -0
  19. data/lib/airbrake-ruby/filters/keys_blacklist.rb +49 -0
  20. data/lib/airbrake-ruby/filters/keys_filter.rb +140 -0
  21. data/lib/airbrake-ruby/filters/keys_whitelist.rb +48 -0
  22. data/lib/airbrake-ruby/filters/root_directory_filter.rb +28 -0
  23. data/lib/airbrake-ruby/filters/sql_filter.rb +104 -0
  24. data/lib/airbrake-ruby/filters/system_exit_filter.rb +23 -0
  25. data/lib/airbrake-ruby/filters/thread_filter.rb +92 -0
  26. data/lib/airbrake-ruby/hash_keyable.rb +37 -0
  27. data/lib/airbrake-ruby/ignorable.rb +44 -0
  28. data/lib/airbrake-ruby/inspectable.rb +39 -0
  29. data/lib/airbrake-ruby/loggable.rb +34 -0
  30. data/lib/airbrake-ruby/monotonic_time.rb +43 -0
  31. data/lib/airbrake-ruby/nested_exception.rb +38 -0
  32. data/lib/airbrake-ruby/notice.rb +162 -0
  33. data/lib/airbrake-ruby/notice_notifier.rb +134 -0
  34. data/lib/airbrake-ruby/performance_breakdown.rb +45 -0
  35. data/lib/airbrake-ruby/performance_notifier.rb +125 -0
  36. data/lib/airbrake-ruby/promise.rb +109 -0
  37. data/lib/airbrake-ruby/query.rb +53 -0
  38. data/lib/airbrake-ruby/request.rb +45 -0
  39. data/lib/airbrake-ruby/response.rb +74 -0
  40. data/lib/airbrake-ruby/stashable.rb +15 -0
  41. data/lib/airbrake-ruby/stat.rb +73 -0
  42. data/lib/airbrake-ruby/sync_sender.rb +113 -0
  43. data/lib/airbrake-ruby/tdigest.rb +393 -0
  44. data/lib/airbrake-ruby/time_truncate.rb +17 -0
  45. data/lib/airbrake-ruby/timed_trace.rb +58 -0
  46. data/lib/airbrake-ruby/truncator.rb +115 -0
  47. data/lib/airbrake-ruby/version.rb +6 -0
  48. data/spec/airbrake_spec.rb +324 -0
  49. data/spec/async_sender_spec.rb +155 -0
  50. data/spec/backtrace_spec.rb +427 -0
  51. data/spec/benchmark_spec.rb +33 -0
  52. data/spec/code_hunk_spec.rb +115 -0
  53. data/spec/config/validator_spec.rb +184 -0
  54. data/spec/config_spec.rb +154 -0
  55. data/spec/deploy_notifier_spec.rb +48 -0
  56. data/spec/file_cache.rb +36 -0
  57. data/spec/filter_chain_spec.rb +92 -0
  58. data/spec/filters/context_filter_spec.rb +23 -0
  59. data/spec/filters/dependency_filter_spec.rb +12 -0
  60. data/spec/filters/exception_attributes_filter_spec.rb +50 -0
  61. data/spec/filters/gem_root_filter_spec.rb +41 -0
  62. data/spec/filters/git_last_checkout_filter_spec.rb +46 -0
  63. data/spec/filters/git_repository_filter.rb +61 -0
  64. data/spec/filters/git_revision_filter_spec.rb +126 -0
  65. data/spec/filters/keys_blacklist_spec.rb +225 -0
  66. data/spec/filters/keys_whitelist_spec.rb +194 -0
  67. data/spec/filters/root_directory_filter_spec.rb +39 -0
  68. data/spec/filters/sql_filter_spec.rb +219 -0
  69. data/spec/filters/system_exit_filter_spec.rb +14 -0
  70. data/spec/filters/thread_filter_spec.rb +277 -0
  71. data/spec/fixtures/notroot.txt +7 -0
  72. data/spec/fixtures/project_root/code.rb +221 -0
  73. data/spec/fixtures/project_root/empty_file.rb +0 -0
  74. data/spec/fixtures/project_root/long_line.txt +1 -0
  75. data/spec/fixtures/project_root/short_file.rb +3 -0
  76. data/spec/fixtures/project_root/vendor/bundle/ignored_file.rb +5 -0
  77. data/spec/helpers.rb +9 -0
  78. data/spec/ignorable_spec.rb +14 -0
  79. data/spec/inspectable_spec.rb +45 -0
  80. data/spec/monotonic_time_spec.rb +12 -0
  81. data/spec/nested_exception_spec.rb +73 -0
  82. data/spec/notice_notifier_spec.rb +356 -0
  83. data/spec/notice_notifier_spec/options_spec.rb +259 -0
  84. data/spec/notice_spec.rb +296 -0
  85. data/spec/performance_breakdown_spec.rb +12 -0
  86. data/spec/performance_notifier_spec.rb +435 -0
  87. data/spec/promise_spec.rb +197 -0
  88. data/spec/query_spec.rb +11 -0
  89. data/spec/request_spec.rb +11 -0
  90. data/spec/response_spec.rb +88 -0
  91. data/spec/spec_helper.rb +100 -0
  92. data/spec/stashable_spec.rb +23 -0
  93. data/spec/stat_spec.rb +47 -0
  94. data/spec/sync_sender_spec.rb +133 -0
  95. data/spec/tdigest_spec.rb +230 -0
  96. data/spec/time_truncate_spec.rb +13 -0
  97. data/spec/timed_trace_spec.rb +125 -0
  98. data/spec/truncator_spec.rb +238 -0
  99. metadata +213 -0
@@ -0,0 +1,259 @@
1
+ RSpec.describe Airbrake::NoticeNotifier do
2
+ let(:project_id) { 105138 }
3
+ let(:project_key) { 'fd04e13d806a90f96614ad8e529b2822' }
4
+ let(:localhost) { 'http://localhost:8080' }
5
+
6
+ let(:endpoint) do
7
+ "https://api.airbrake.io/api/v3/projects/#{project_id}/notices"
8
+ end
9
+
10
+ let(:params) { {} }
11
+ let(:ex) { AirbrakeTestError.new }
12
+
13
+ before do
14
+ stub_request(:post, endpoint).to_return(status: 201, body: '{}')
15
+
16
+ Airbrake::Config.instance = Airbrake::Config.new(
17
+ project_id: project_id,
18
+ project_key: project_key
19
+ )
20
+ end
21
+
22
+ describe "options" do
23
+ describe ":host" do
24
+ context "when custom" do
25
+ shared_examples 'endpoint' do |host, endpoint, title|
26
+ before { Airbrake::Config.instance.merge(host: host) }
27
+
28
+ example(title) do
29
+ stub_request(:post, endpoint).to_return(status: 201, body: '{}')
30
+ subject.notify_sync(ex)
31
+
32
+ expect(a_request(:post, endpoint)).to have_been_made.once
33
+ end
34
+ end
35
+
36
+ path = '/api/v3/projects/105138/notices'
37
+
38
+ context "given a full host" do
39
+ include_examples('endpoint', localhost = 'http://localhost:8080',
40
+ URI.join(localhost, path),
41
+ "sends notices to the specified host's endpoint")
42
+ end
43
+
44
+ context "given a full host" do
45
+ include_examples('endpoint', localhost = 'http://localhost',
46
+ URI.join(localhost, path),
47
+ "assumes port 80 by default")
48
+ end
49
+
50
+ context "given a host without scheme" do
51
+ include_examples 'endpoint', localhost = 'localhost:8080',
52
+ URI.join("https://#{localhost}", path),
53
+ "assumes https by default"
54
+ end
55
+
56
+ context "given only hostname" do
57
+ include_examples 'endpoint', localhost = 'localhost',
58
+ URI.join("https://#{localhost}", path),
59
+ "assumes https and port 80 by default"
60
+ end
61
+ end
62
+ end
63
+
64
+ describe ":root_directory" do
65
+ before do
66
+ subject.add_filter(
67
+ Airbrake::Filters::RootDirectoryFilter.new('/home/kyrylo/code')
68
+ )
69
+ end
70
+
71
+ it "filters out frames" do
72
+ subject.notify_sync(ex)
73
+
74
+ expect(
75
+ a_request(:post, endpoint)
76
+ .with(body: %r|{"file":"/PROJECT_ROOT/airbrake/ruby/spec/airbrake_spec.+|)
77
+ ).to have_been_made.once
78
+ end
79
+
80
+ context "when present and is a" do
81
+ shared_examples 'root directory' do |dir|
82
+ before { Airbrake::Config.instance.merge(root_directory: dir) }
83
+
84
+ it "being included into the notice's payload" do
85
+ subject.notify_sync(ex)
86
+ expect(
87
+ a_request(:post, endpoint)
88
+ .with(body: %r{"rootDirectory":"/bingo/bango"})
89
+ ).to have_been_made.once
90
+ end
91
+ end
92
+
93
+ context "String" do
94
+ include_examples 'root directory', '/bingo/bango'
95
+ end
96
+
97
+ context "Pathname" do
98
+ include_examples 'root directory', Pathname.new('/bingo/bango')
99
+ end
100
+ end
101
+ end
102
+
103
+ describe ":proxy" do
104
+ let(:proxy) do
105
+ WEBrick::HTTPServer.new(
106
+ Port: 0,
107
+ Logger: WEBrick::Log.new('/dev/null'),
108
+ AccessLog: []
109
+ )
110
+ end
111
+
112
+ let(:requests) { Queue.new }
113
+
114
+ let(:proxy_params) do
115
+ { host: 'localhost',
116
+ port: proxy.config[:Port],
117
+ user: 'user',
118
+ password: 'password' }
119
+ end
120
+
121
+ before do
122
+ Airbrake::Config.instance.merge(
123
+ proxy: proxy_params,
124
+ host: "http://localhost:#{proxy.config[:Port]}"
125
+ )
126
+
127
+ proxy.mount_proc '/' do |req, res|
128
+ requests << req
129
+ res.status = 201
130
+ res.body = "OK\n"
131
+ end
132
+
133
+ Thread.new { proxy.start }
134
+ end
135
+
136
+ after { proxy.stop }
137
+
138
+ it "is being used if configured" do
139
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0")
140
+ skip(
141
+ "We use Webmock 2, which doesn't support Ruby 2.6+. It's " \
142
+ "safe to run this test on 2.6+ once we upgrade to Webmock 3.5+"
143
+ )
144
+ end
145
+ subject.notify_sync(ex)
146
+
147
+ proxied_request = requests.pop(true)
148
+
149
+ expect(proxied_request.header['proxy-authorization'].first)
150
+ .to eq('Basic dXNlcjpwYXNzd29yZA==')
151
+
152
+ # rubocop:disable Metrics/LineLength
153
+ expect(proxied_request.request_line)
154
+ .to eq("POST http://localhost:#{proxy.config[:Port]}/api/v3/projects/105138/notices HTTP/1.1\r\n")
155
+ # rubocop:enable Metrics/LineLength
156
+ end
157
+ end
158
+
159
+ describe ":environment" do
160
+ context "when present" do
161
+ before { Airbrake::Config.instance.merge(environment: :production) }
162
+
163
+ it "being included into the notice's payload" do
164
+ subject.notify_sync(ex)
165
+ expect(
166
+ a_request(:post, endpoint)
167
+ .with(body: /"context":{.*"environment":"production".*}/)
168
+ ).to have_been_made.once
169
+ end
170
+ end
171
+ end
172
+
173
+ describe ":ignore_environments" do
174
+ shared_examples 'sent notice' do |params|
175
+ before { Airbrake::Config.instance.merge(params) }
176
+
177
+ it "sends a notice" do
178
+ subject.notify_sync(ex)
179
+ expect(a_request(:post, endpoint)).to have_been_made
180
+ end
181
+ end
182
+
183
+ shared_examples 'ignored notice' do |params|
184
+ before { Airbrake::Config.instance.merge(params) }
185
+
186
+ it "ignores exceptions occurring in envs that were not configured" do
187
+ subject.notify_sync(ex)
188
+ expect(a_request(:post, endpoint)).not_to have_been_made
189
+ end
190
+ end
191
+
192
+ context "when env is set and ignore_environments doesn't mention it" do
193
+ params = {
194
+ environment: :development,
195
+ ignore_environments: [:production]
196
+ }
197
+
198
+ include_examples 'sent notice', params
199
+ end
200
+
201
+ context "when the current env and notify envs are the same" do
202
+ params = {
203
+ environment: :development,
204
+ ignore_environments: %i[production development]
205
+ }
206
+
207
+ include_examples 'ignored notice', params
208
+
209
+ it "returns early and doesn't try to parse the given exception" do
210
+ expect(Airbrake::Notice).not_to receive(:new)
211
+ expect(subject.notify_sync(ex))
212
+ .to eq('error' => "current environment 'development' is ignored")
213
+ expect(a_request(:post, endpoint)).not_to have_been_made
214
+ end
215
+ end
216
+
217
+ context "when the current env is not set and notify envs are present" do
218
+ params = { ignore_environments: %i[production development] }
219
+
220
+ include_examples 'sent notice', params
221
+ end
222
+
223
+ context "when the current env is set and notify envs aren't" do
224
+ include_examples 'sent notice', environment: :development
225
+ end
226
+
227
+ context "when ignore_environments specifies a Regexp pattern" do
228
+ params = {
229
+ environment: :testing,
230
+ ignore_environments: ['staging', /test.+/]
231
+ }
232
+
233
+ include_examples 'ignored notice', params
234
+ end
235
+ end
236
+
237
+ describe ":blacklist_keys" do
238
+ # Fixes https://github.com/airbrake/airbrake-ruby/issues/276
239
+ context "when specified along with :whitelist_keys" do
240
+ context "and when context payload is present" do
241
+ before do
242
+ Airbrake::Config.instance.merge(
243
+ blacklist_keys: %i[password password_confirmation],
244
+ whitelist_keys: [:email, /user/i, 'account_id']
245
+ )
246
+ end
247
+
248
+ it "sends a notice" do
249
+ notice = subject.build_notice(ex)
250
+ notice[:context][:headers] = 'banana'
251
+ subject.notify_sync(notice)
252
+
253
+ expect(a_request(:post, endpoint)).to have_been_made
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,296 @@
1
+ RSpec.describe Airbrake::Notice do
2
+ let(:notice) { described_class.new(AirbrakeTestError.new, bingo: '1') }
3
+
4
+ describe "#to_json" do
5
+ context "app_version" do
6
+ context "when missing" do
7
+ before { Airbrake::Config.instance.merge(app_version: nil) }
8
+
9
+ it "doesn't include app_version" do
10
+ expect(notice.to_json).not_to match(/"context":{"version":"1.2.3"/)
11
+ end
12
+ end
13
+
14
+ context "when present" do
15
+ let(:notice) { described_class.new(AirbrakeTestError.new) }
16
+
17
+ before do
18
+ Airbrake::Config.instance.merge(
19
+ app_version: "1.2.3",
20
+ root_directory: "/one/two"
21
+ )
22
+ end
23
+
24
+ it "includes app_version" do
25
+ expect(notice.to_json).to match(/"context":{"version":"1.2.3"/)
26
+ end
27
+
28
+ it "includes root_directory" do
29
+ expect(notice.to_json).to match(%r{"rootDirectory":"/one/two"})
30
+ end
31
+ end
32
+ end
33
+
34
+ context "when versions is empty" do
35
+ it "doesn't set the 'versions' payload" do
36
+ expect(notice.to_json).not_to match(
37
+ /"context":{"versions":{"dep":"1.2.3"}}/
38
+ )
39
+ end
40
+ end
41
+
42
+ context "when versions is not empty" do
43
+ it "sets the 'versions' payload" do
44
+ notice[:context][:versions] = { 'dep' => '1.2.3' }
45
+ expect(notice.to_json).to match(
46
+ /"context":{.*"versions":{"dep":"1.2.3"}.*}/
47
+ )
48
+ end
49
+ end
50
+
51
+ context "truncation" do
52
+ shared_examples 'payloads' do |size, msg|
53
+ it msg do
54
+ ex = AirbrakeTestError.new
55
+
56
+ backtrace = []
57
+ size.times { backtrace << "bin/rails:3:in `<main>'" }
58
+ ex.set_backtrace(backtrace)
59
+
60
+ notice = described_class.new(ex)
61
+
62
+ expect(notice.to_json.bytesize).to be < 64000
63
+ end
64
+ end
65
+
66
+ max_msg = 'truncates to the max allowed size'
67
+
68
+ context "with an extremely huge payload" do
69
+ include_examples 'payloads', 200_000, max_msg
70
+ end
71
+
72
+ context "with a big payload" do
73
+ include_examples 'payloads', 50_000, max_msg
74
+ end
75
+
76
+ small_msg = "doesn't truncate it"
77
+
78
+ context "with a small payload" do
79
+ include_examples 'payloads', 1000, small_msg
80
+ end
81
+
82
+ context "with a tiny payload" do
83
+ include_examples 'payloads', 300, small_msg
84
+ end
85
+
86
+ context "when truncation failed" do
87
+ it "returns nil" do
88
+ expect_any_instance_of(Airbrake::Truncator)
89
+ .to receive(:reduce_max_size).and_return(0)
90
+
91
+ encoded = Base64.encode64("\xD3\xE6\xBC\x9D\xBA").encode!('ASCII-8BIT')
92
+ bad_string = Base64.decode64(encoded)
93
+
94
+ ex = AirbrakeTestError.new
95
+
96
+ backtrace = []
97
+ 10.times { backtrace << "bin/rails:3:in `<#{bad_string}>'" }
98
+ ex.set_backtrace(backtrace)
99
+
100
+ notice = described_class.new(ex)
101
+ expect(notice.to_json).to be_nil
102
+ end
103
+ end
104
+
105
+ describe "object replacement with its string version" do
106
+ let(:klass) { Class.new {} }
107
+ let(:ex) { AirbrakeTestError.new }
108
+ let(:params) { { bingo: [Object.new, klass.new] } }
109
+ let(:notice) { described_class.new(ex, params) }
110
+
111
+ before do
112
+ backtrace = []
113
+ backtrace_size.times { backtrace << "bin/rails:3:in `<main>'" }
114
+ ex.set_backtrace(backtrace)
115
+ end
116
+
117
+ context "with payload within the limits" do
118
+ let(:backtrace_size) { 1000 }
119
+
120
+ it "doesn't happen" do
121
+ expect(notice.to_json)
122
+ .to match(/bingo":\["#<Object:.+>","#<#<Class:.+>:.+>"/)
123
+ end
124
+ end
125
+
126
+ context "with payload bigger than the limit" do
127
+ context "with payload within the limits" do
128
+ let(:backtrace_size) { 50_000 }
129
+
130
+ it "happens" do
131
+ expect(notice.to_json)
132
+ .to match(/bingo":\[".+Object.+",".+Class.+"/)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ context "given a closed IO object" do
140
+ context "and when it is not monkey-patched by ActiveSupport" do
141
+ it "is not getting truncated" do
142
+ notice[:params] = { obj: IO.new(0).tap(&:close) }
143
+ expect(notice.to_json).to match(/"obj":"#<IO:0x.+>"/)
144
+ end
145
+ end
146
+
147
+ context "and when it is monkey-patched by ActiveSupport" do
148
+ # Instances of this class contain a closed IO object assigned to an
149
+ # instance variable. Normally, the JSON gem, which we depend on can
150
+ # parse closed IO objects. However, because ActiveSupport monkey-patches
151
+ # #to_json and calls #to_a on them, they raise IOError when we try to
152
+ # serialize them.
153
+ #
154
+ # @see https://goo.gl/0A3xNC
155
+ class ObjectWithIoIvars
156
+ def initialize
157
+ @bongo = Tempfile.new('bongo').tap(&:close)
158
+ end
159
+
160
+ # @raise [NotImplementedError] when inside a Rails environment
161
+ def to_json(*)
162
+ raise NotImplementedError
163
+ end
164
+ end
165
+
166
+ # @see ObjectWithIoIvars
167
+ class ObjectWithNestedIoIvars
168
+ def initialize
169
+ @bish = ObjectWithIoIvars.new
170
+ end
171
+
172
+ # @see ObjectWithIoIvars#to_json
173
+ def to_json(*)
174
+ raise NotImplementedError
175
+ end
176
+ end
177
+
178
+ context "and also when it's a closed Tempfile" do
179
+ it "doesn't fail" do
180
+ notice[:params] = { obj: Tempfile.new('bongo').tap(&:close) }
181
+ expect(notice.to_json).to match(/"obj":"#<(Temp)?file:0x.+>"/i)
182
+ end
183
+ end
184
+
185
+ context "and also when it's an IO ivar" do
186
+ it "doesn't fail" do
187
+ notice[:params] = { obj: ObjectWithIoIvars.new }
188
+ expect(notice.to_json).to match(/"obj":".+ObjectWithIoIvars.+"/)
189
+ end
190
+
191
+ context "and when it's deeply nested inside a hash" do
192
+ it "doesn't fail" do
193
+ notice[:params] = { a: { b: { c: ObjectWithIoIvars.new } } }
194
+ expect(notice.to_json).to match(
195
+ /"params":{"a":{"b":{"c":".+ObjectWithIoIvars.+"}}.*}/
196
+ )
197
+ end
198
+ end
199
+
200
+ context "and when it's deeply nested inside an array" do
201
+ it "doesn't fail" do
202
+ notice[:params] = { a: [[ObjectWithIoIvars.new]] }
203
+ expect(notice.to_json).to match(
204
+ /"params":{"a":\[\[".+ObjectWithIoIvars.+"\]\].*}/
205
+ )
206
+ end
207
+ end
208
+ end
209
+
210
+ context "and also when it's a non-IO ivar, which contains an IO ivar itself" do
211
+ it "doesn't fail" do
212
+ notice[:params] = { obj: ObjectWithNestedIoIvars.new }
213
+ expect(notice.to_json).to match(/"obj":".+ObjectWithNested.+"/)
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ it "overwrites the 'notifier' payload with the default values" do
220
+ notice[:notifier] = { name: 'bingo', bango: 'bongo' }
221
+
222
+ expect(notice.to_json)
223
+ .to match(/"notifier":{"name":"airbrake-ruby","version":".+","url":".+"}/)
224
+ end
225
+
226
+ it "always contains context/hostname" do
227
+ expect(notice.to_json)
228
+ .to match(/"context":{.*"hostname":".+".*}/)
229
+ end
230
+
231
+ it "defaults to the error severity" do
232
+ expect(notice.to_json).to match(/"context":{.*"severity":"error".*}/)
233
+ end
234
+
235
+ it "always contains environment/program_name" do
236
+ expect(notice.to_json)
237
+ .to match(%r|"environment":{"program_name":.+/rspec.*|)
238
+ end
239
+
240
+ it "contains errors" do
241
+ expect(notice.to_json)
242
+ .to match(/"errors":\[{"type":"AirbrakeTestError","message":"App crash/)
243
+ end
244
+
245
+ it "contains a backtrace" do
246
+ expect(notice.to_json)
247
+ .to match(%r|"backtrace":\[{"file":"/home/.+/spec/spec_helper.rb"|)
248
+ end
249
+
250
+ it "contains params" do
251
+ expect(notice.to_json).to match(/"params":{"bingo":"1"}/)
252
+ end
253
+ end
254
+
255
+ describe "#[]" do
256
+ it "accesses payload" do
257
+ expect(notice[:params]).to eq(bingo: '1')
258
+ end
259
+
260
+ it "raises error if notice is ignored" do
261
+ notice.ignore!
262
+ expect { notice[:params] }
263
+ .to raise_error(Airbrake::Error, 'cannot access ignored Airbrake::Notice')
264
+ end
265
+ end
266
+
267
+ describe "#[]=" do
268
+ it "sets a payload value" do
269
+ hash = { bingo: 'bango' }
270
+ notice[:params] = hash
271
+ expect(notice[:params]).to equal(hash)
272
+ end
273
+
274
+ it "raises error if notice is ignored" do
275
+ notice.ignore!
276
+ expect { notice[:params] = {} }
277
+ .to raise_error(Airbrake::Error, 'cannot access ignored Airbrake::Notice')
278
+ end
279
+
280
+ it "raises error when trying to assign unrecognized key" do
281
+ expect { notice[:bingo] = 1 }
282
+ .to raise_error(Airbrake::Error, /:bingo is not recognized among/)
283
+ end
284
+
285
+ it "raises when setting non-hash objects as the value" do
286
+ expect { notice[:params] = Object.new }
287
+ .to raise_error(Airbrake::Error, 'Got Object value, wanted a Hash')
288
+ end
289
+ end
290
+
291
+ describe "#stash" do
292
+ subject { described_class.new(AirbrakeTestError.new) }
293
+
294
+ it { is_expected.to respond_to(:stash) }
295
+ end
296
+ end