airbrake-ruby 3.2.2-java

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