1# frozen_string_literal: true
2
3require "test_helper"
4require_relative "../sgx-bwmsgsv2"
5
6def panic(e)
7 $panic = e
8end
9
10class ComponentTest < Minitest::Test
11 def setup
12 SGXbwmsgsv2.instance_variable_set(:@written, [])
13
14 def SGXbwmsgsv2.write_to_stream(s)
15 @written ||= []
16 @written << s
17 end
18
19 REDIS.reset!
20 REDIS.set("catapult_jid-", "HERE")
21 REDIS.set("catapult_jid-+15550000000", "test@example.com")
22 REDIS.set("catapult_cred-test@example.com", [
23 'account', 'user', 'password', '+15550000000'
24 ])
25 end
26
27 def written
28 SGXbwmsgsv2.instance_variable_get(:@written)
29 end
30
31 def xmpp_error_name(error)
32 error.find_first(
33 "child::*[name()!='text']",
34 Blather::StanzaError::STANZA_ERR_NS
35 ).element_name
36 end
37
38 def xmpp_error_text(error)
39 error.find_first("ns:text", ns: Blather::StanzaError::STANZA_ERR_NS)&.text
40 end
41
42 def process_stanza(s)
43 SGXbwmsgsv2.send(:client).receive_data(s)
44 raise $panic if $panic
45 end
46
47 def test_message_unregistered
48 m = Blather::Stanza::Message.new("+15551234567@component", "a"*4096)
49 m.from = "unknown@example.com"
50 process_stanza(m)
51
52 assert_equal 1, written.length
53
54 stanza = Blather::XMPPNode.parse(written.first.to_xml)
55 assert stanza.error?
56 error = stanza.find_first("error")
57 assert_equal "auth", error["type"]
58 assert_equal "registration-required", xmpp_error_name(error)
59 end
60 em :test_message_unregistered
61
62 def test_message_too_long
63 req = stub_request(
64 :post,
65 "https://messaging.bandwidth.com/api/v2/users/account/messages"
66 ).with(body: {
67 from: "+15550000000",
68 to: "+15551234567",
69 text: "a"*4096,
70 applicationId: nil,
71 tag: " "
72 }).to_return(status: 400, body: JSON.dump(
73 description: "Bad text.",
74 fieldErrors: [{ description: "4096 not allowed" }]
75 ))
76
77 m = Blather::Stanza::Message.new("+15551234567@component", "a"*4096)
78 m.from = "test@example.com"
79 process_stanza(m)
80
81 assert_requested req
82 assert_equal 1, written.length
83
84 stanza = Blather::XMPPNode.parse(written.first.to_xml)
85 assert stanza.error?
86 error = stanza.find_first("error")
87 assert_equal "cancel", error["type"]
88 assert_equal "internal-server-error", xmpp_error_name(error)
89 assert_equal "Bad text. 4096 not allowed", xmpp_error_text(error)
90 end
91 em :test_message_too_long
92
93 def test_message_to_component_not_group
94 m = Blather::Stanza::Message.new("component", "a"*4096)
95 m.from = "test@example.com"
96 process_stanza(m)
97
98 assert_equal 1, written.length
99
100 stanza = Blather::XMPPNode.parse(written.first.to_xml)
101 assert stanza.error?
102 error = stanza.find_first("error")
103 assert_equal "cancel", error["type"]
104 assert_equal "item-not-found", xmpp_error_name(error)
105 end
106 em :test_message_to_component_not_group
107
108 def test_message_to_invalid_num
109 m = Blather::Stanza::Message.new("123@component", "a"*4096)
110 m.from = "test@example.com"
111 process_stanza(m)
112
113 assert_equal 1, written.length
114
115 stanza = Blather::XMPPNode.parse(written.first.to_xml)
116 assert stanza.error?
117 error = stanza.find_first("error")
118 assert_equal "cancel", error["type"]
119 assert_equal "item-not-found", xmpp_error_name(error)
120 end
121 em :test_message_to_invalid_num
122
123 def test_message_to_anonymous
124 m = Blather::Stanza::Message.new(
125 "1;phone-context=anonymous.phone-context.soprani.ca@component",
126 "a"*4096
127 )
128 m.from = "test@example.com"
129 process_stanza(m)
130
131 assert_equal 1, written.length
132
133 stanza = Blather::XMPPNode.parse(written.first.to_xml)
134 assert stanza.error?
135 error = stanza.find_first("error")
136 assert_equal "cancel", error["type"]
137 assert_equal "gone", xmpp_error_name(error)
138 end
139 em :test_message_to_anonymous
140
141 def test_blank_message
142 m = Blather::Stanza::Message.new("+15551234567@component", " ")
143 m.from = "test@example.com"
144 process_stanza(m)
145
146 assert_equal 1, written.length
147
148 stanza = Blather::XMPPNode.parse(written.first.to_xml)
149 assert stanza.error?
150 error = stanza.find_first("error")
151 assert_equal "modify", error["type"]
152 assert_equal "policy-violation", xmpp_error_name(error)
153 end
154 em :test_blank_message
155
156 def test_ibr_bad_tel
157 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
158 iq.from = "newuser@example.com"
159 iq.phone = "5551234567"
160 process_stanza(iq)
161
162 assert_equal 1, written.length
163
164 stanza = Blather::XMPPNode.parse(written.first.to_xml)
165 assert stanza.error?
166 error = stanza.find_first("error")
167 assert_equal "cancel", error["type"]
168 assert_equal "item-not-found", xmpp_error_name(error)
169 end
170 em :test_ibr_bad_tel
171
172 def test_ibr_bad_creds
173 stub_request(
174 :get,
175 "https://messaging.bandwidth.com/api/v2/users/acct/media"
176 ).with(basic_auth: ["user", "pw"]).to_return(status: 401)
177
178 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
179 iq.from = "newuser@example.com"
180 iq.phone = "+15551234567"
181 iq.nick = "acct"
182 iq.username = "user"
183 iq.password = "pw"
184 process_stanza(iq)
185
186 assert_equal 1, written.length
187
188 stanza = Blather::XMPPNode.parse(written.first.to_xml)
189 assert stanza.error?
190 error = stanza.find_first("error")
191 assert_equal "auth", error["type"]
192 assert_equal "not-authorized", xmpp_error_name(error)
193 end
194 em :test_ibr_bad_creds
195
196 def test_ibr_number_not_found
197 stub_request(
198 :get,
199 "https://messaging.bandwidth.com/api/v2/users/acct/media"
200 ).with(basic_auth: ["user", "pw"]).to_return(status: 404)
201
202 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
203 iq.from = "newuser@example.com"
204 iq.phone = "+15551234567"
205 iq.nick = "acct"
206 iq.username = "user"
207 iq.password = "pw"
208 process_stanza(iq)
209
210 assert_equal 1, written.length
211
212 stanza = Blather::XMPPNode.parse(written.first.to_xml)
213 assert stanza.error?
214 error = stanza.find_first("error")
215 assert_equal "cancel", error["type"]
216 assert_equal "item-not-found", xmpp_error_name(error)
217 end
218 em :test_ibr_number_not_found
219
220 def test_ibr_other_error
221 stub_request(
222 :get,
223 "https://messaging.bandwidth.com/api/v2/users/acct/media"
224 ).with(basic_auth: ["user", "pw"]).to_return(status: 400)
225
226 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
227 iq.from = "newuser@example.com"
228 iq.phone = "+15551234567"
229 iq.nick = "acct"
230 iq.username = "user"
231 iq.password = "pw"
232 process_stanza(iq)
233
234 assert_equal 1, written.length
235
236 stanza = Blather::XMPPNode.parse(written.first.to_xml)
237 assert stanza.error?
238 error = stanza.find_first("error")
239 assert_equal "modify", error["type"]
240 assert_equal "not-acceptable", xmpp_error_name(error)
241 end
242 em :test_ibr_other_error
243
244 def test_ibr_new
245 stub_request(
246 :get,
247 "https://messaging.bandwidth.com/api/v2/users/acct/media"
248 ).with(basic_auth: ["user", "pw"]).to_return(status: 200, body: "[]")
249
250 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
251 iq.from = "test9@example.com"
252 iq.phone = "+15550000009"
253 iq.nick = "acct"
254 iq.username = "user"
255 iq.password = "pw"
256 process_stanza(iq)
257
258 assert_equal 1, written.length
259
260 stanza = Blather::XMPPNode.parse(written.first.to_xml)
261 refute stanza.error?
262 assert_equal(
263 ["acct", "user", "pw", "+15550000009"],
264 REDIS.get("catapult_cred-test9@example.com").sync
265 )
266 assert_equal "test9@example.com", REDIS.get("catapult_jid-+15550000009").sync
267 assert REDIS.get("catapult_jid-").sync
268 end
269 em :test_ibr_new
270
271 def test_ibr_update
272 stub_request(
273 :get,
274 "https://messaging.bandwidth.com/api/v2/users/acct/media"
275 ).with(basic_auth: ["user", "pw"]).to_return(status: 200, body: "[]")
276
277 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
278 iq.from = "test@example.com"
279 iq.phone = "+15550000009"
280 iq.nick = "acct"
281 iq.username = "user"
282 iq.password = "pw"
283 process_stanza(iq)
284
285 assert_equal 1, written.length
286
287 stanza = Blather::XMPPNode.parse(written.first.to_xml)
288 refute stanza.error?
289 assert_equal(
290 ["acct", "user", "pw", "+15550000009"],
291 REDIS.get("catapult_cred-test@example.com").sync
292 )
293 assert_equal "test@example.com", REDIS.get("catapult_jid-+15550000009").sync
294 refute REDIS.get("catapult_jid-+15550000000").sync
295 end
296 em :test_ibr_update
297
298 def test_ibr_conflict
299 stub_request(
300 :get,
301 "https://messaging.bandwidth.com/api/v2/users/acct/media"
302 ).with(basic_auth: ["user", "pw"]).to_return(status: 200, body: "[]")
303
304 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
305 iq.from = "test2@example.com"
306 iq.phone = "+15550000000"
307 iq.nick = "acct"
308 iq.username = "user"
309 iq.password = "pw"
310 process_stanza(iq)
311
312 assert_equal 1, written.length
313
314 stanza = Blather::XMPPNode.parse(written.first.to_xml)
315 assert stanza.error?
316 error = stanza.find_first("error")
317 assert_equal "cancel", error["type"]
318 assert_equal "conflict", xmpp_error_name(error)
319 assert_equal(
320 "Another user exists for +15550000000",
321 xmpp_error_text(error)
322 )
323 end
324 em :test_ibr_conflict
325
326 def test_ibr_remove
327 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
328 iq.from = "test@example.com"
329 iq.remove!
330 process_stanza(iq)
331
332 refute REDIS.get("catapult_cred-test@example.com").sync
333
334 assert_equal 1, written.length
335
336 stanza = Blather::XMPPNode.parse(written.first.to_xml)
337 assert stanza.result?
338 end
339 em :test_ibr_remove
340
341 def test_ibr_form
342 stub_request(
343 :get,
344 "https://messaging.bandwidth.com/api/v2/users/acct/media"
345 ).with(basic_auth: ["user", "pw"]).to_return(status: 200, body: "[]")
346
347 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
348 iq.from = "formuser@example.com"
349 form = Blather::Stanza::X.find_or_create(iq.query)
350 form.fields = [
351 {
352 var: "nick",
353 value: "acct"
354 },
355 {
356 var: "username",
357 value: "user"
358 },
359 {
360 var: "password",
361 value: "pw"
362 },
363 {
364 var: "phone",
365 value: "+15551234567"
366 }
367 ]
368 process_stanza(iq)
369
370 assert_equal(
371 ["acct", "user", "pw", "+15551234567"],
372 REDIS.get("catapult_cred-formuser@example.com").sync
373 )
374
375 assert_equal(
376 "formuser@example.com",
377 REDIS.get("catapult_jid-+15551234567").sync
378 )
379
380 assert_equal 1, written.length
381 stanza = Blather::XMPPNode.parse(written.first.to_xml)
382 assert stanza.result?
383 end
384 em :test_ibr_form
385
386 def test_ibr_get_form_registered
387 iq = Blather::Stanza::Iq::IBR.new(:get, "component")
388 iq.from = "test@example.com"
389 process_stanza(iq)
390
391 assert_equal 1, written.length
392 stanza = Blather::XMPPNode.parse(written.first.to_xml)
393 assert stanza.result?
394 assert stanza.registered?
395 assert_equal(
396 ["nick", "username", "password", "phone"],
397 stanza.form.fields.map(&:var)
398 )
399 assert stanza.instructions
400 assert stanza.nick
401 assert stanza.username
402 assert stanza.password
403 assert stanza.phone
404 refute stanza.email
405 end
406 em :test_ibr_get_form_registered
407
408 def test_port_out_pin
409 iq = Blather::Stanza::Iq::Command.new(:set, 'component').tap do |iq|
410 iq.from = 'test@example.com'
411 iq.node = 'set-port-out-pin'
412 iq.sessionid = 'test-session-123'
413 iq.action = :complete
414 iq.form.type = :submit
415 iq.form.fields = [
416 {
417 var: 'pin',
418 value: '1234'
419 },
420 {
421 var: 'confirm_pin',
422 value: '1234'
423 }
424 ]
425 end
426
427 tn_mock = Minitest::Mock.new
428 tn_mock.expect(:get_details, { tier: 0.0, on_net_vendor: true })
429 with_stubs([
430 [
431 BandwidthIris::TnOptions,
432 :create_tn_option_order,
433 ->(client, data) { {order_id: 'test-order-123', processing_status: 'RECEIVED', error_list: {}} }
434 ],
435 [
436 BandwidthIris::TnOptions,
437 :get_tn_option_order,
438 ->(client, order_id) { {order_id: order_id, order_status: 'COMPLETE', error_list: {}} }
439 ],
440 [
441 BandwidthIris::Tn,
442 :get,
443 tn_mock
444 ]
445 ]) do
446 process_stanza(iq)
447
448 assert_equal 1, written.length
449
450 stanza = Blather::XMPPNode.parse(written.first.to_xml)
451 refute stanza.error?
452 assert_mock tn_mock
453 end
454 end
455 em :test_port_out_pin
456
457 def test_port_out_pin_mismatch
458 iq = Blather::Stanza::Iq::Command.new(:set, 'component').tap do |iq|
459 iq.from = 'test@example.com'
460 iq.node = 'set-port-out-pin'
461 iq.sessionid = 'test-session-mismatch'
462 iq.action = :complete
463 iq.form.type = :submit
464 iq.form.fields = [
465 {
466 var: 'pin',
467 value: '1234'
468 },
469 {
470 var: 'confirm_pin',
471 value: '5678'
472 }
473 ]
474 end
475
476 process_stanza(iq)
477
478 assert_equal 1, written.length
479
480 stanza = Blather::XMPPNode.parse(written.first.to_xml)
481 assert_equal :error, stanza.type
482 error = stanza.find_first("error")
483 assert_equal "modify", error["type"]
484 assert_equal "bad-request", xmpp_error_name(error)
485 assert_equal "PIN confirmation does not match", xmpp_error_text(error)
486 end
487 em :test_port_out_pin_mismatch
488
489 def test_port_out_pin_validation
490 [
491 ['123', 'PIN must be 4-10 alphanumeric characters'],
492 ['12345678901', 'PIN must be 4-10 alphanumeric characters'],
493 ['123!', 'PIN must be 4-10 alphanumeric characters'],
494 ['pin with spaces', 'PIN must be 4-10 alphanumeric characters']
495 ].each do |invalid_pin, expected_error|
496 iq = Blather::Stanza::Iq::Command.new(:set, 'component').tap do |iq|
497 iq.from = 'test@example.com'
498 iq.node = 'set-port-out-pin'
499 iq.sessionid = "test-session-validation-#{invalid_pin.gsub(/[^a-zA-Z0-9]/, '')}"
500 iq.action = :complete
501 iq.form.type = :submit
502 iq.form.fields = [
503 {
504 var: 'pin',
505 value: invalid_pin
506 },
507 {
508 var: 'confirm_pin',
509 value: invalid_pin
510 }
511 ]
512 end
513
514 process_stanza(iq)
515
516 assert_equal 1, written.length, "Failed for PIN: #{invalid_pin}"
517
518 stanza = Blather::XMPPNode.parse(written.first.to_xml)
519 assert_equal :error, stanza.type, "Expected error for PIN: #{invalid_pin}"
520 error = stanza.find_first("error")
521 assert_equal "modify", error["type"]
522 assert_equal "bad-request", xmpp_error_name(error)
523 assert_equal expected_error, xmpp_error_text(error),
524 "Wrong error message for PIN: #{invalid_pin}"
525
526 SGXbwmsgsv2.instance_variable_set(:@written, [])
527 end
528 end
529 em :test_port_out_pin_validation
530
531 def test_outbound_message_emits_to_stream
532 stub_request(
533 :post,
534 "https://messaging.bandwidth.com/api/v2/users/account/messages"
535 ).with(body: hash_including(
536 from: "+15550000000",
537 to: "+15551234567",
538 text: "Hello world"
539 )).to_return(status: 201, body: JSON.dump(id: "bw-msg-123"))
540
541 m = Blather::Stanza::Message.new("+15551234567@component", "Hello world")
542 m.from = "test@example.com/resource"
543 m['id'] = "stanza-123"
544 process_stanza(m)
545
546 entries = REDIS.stream_entries("messages").sync
547 assert_equal 1, entries.length
548
549 event = entries.first[:fields]
550 assert_equal "out", event["event"]
551 assert_equal "+15550000000", event["from"]
552 assert_equal "+15550000000", event["owner"]
553 assert_equal JSON.dump(["+15551234567"]), event["to"]
554 assert_equal "stanza-123", event["stanza_id"]
555 assert_equal "bw-msg-123", event["bandwidth_id"]
556 assert_equal "Hello world", event["body"]
557 end
558 em :test_outbound_message_emits_to_stream
559
560 def test_passthrough_message_emits_to_stream
561 REDIS.set("catapult_jid-+15559999999", "other@example.com")
562 REDIS.set("catapult_cred-other@example.com", [
563 'other_acct', 'other_user', 'other_pw', '+15559999999'
564 ])
565
566 m = Blather::Stanza::Message.new("+15559999999@component", "Pass through")
567 m.from = "test@example.com/resource"
568 m['id'] = "passthru-stanza-456"
569 process_stanza(m)
570
571 entries = REDIS.stream_entries("messages").sync
572 assert_equal 1, entries.length
573
574 event = entries.first[:fields]
575 assert_equal "thru", event["event"]
576 assert_equal "+15550000000", event["from"]
577 assert_equal JSON.dump(["+15559999999"]), event["to"]
578 assert_equal "passthru-stanza-456", event["stanza_id"]
579 assert_equal "Pass through", event["body"]
580 refute event.key?("timestamp"), "Thru events should not have a timestamp field"
581 end
582 em :test_passthrough_message_emits_to_stream
583
584 def invoke_webhook(payload)
585 handler = WebhookHandler.new
586 env = {
587 "REQUEST_URI" => "/",
588 "REQUEST_METHOD" => "POST",
589 "params" => {"_json" => [payload]}
590 }
591 handler.instance_variable_set(:@env, env)
592 def handler.params
593 @env["params"]
594 end
595
596 EMPromise.resolve(nil).then {
597 handler.response(env)
598 }.sync
599 end
600
601 def test_inbound_sms_emits_to_stream
602 payload = {
603 "type" => "message-received",
604 "message" => {
605 "id" => "bw-in-123",
606 "direction" => "in",
607 "owner" => "+15550000000",
608 "from" => "+15551234567",
609 "to" => ["+15550000000"],
610 "time" => "2025-01-13T10:00:00Z",
611 "text" => "Hello from outside"
612 }
613 }
614
615 invoke_webhook(payload)
616
617 entries = REDIS.stream_entries("messages").sync
618 assert_equal 1, entries.length
619
620 event = entries.first[:fields]
621 assert_equal "in", event["event"]
622 assert_equal "+15551234567", event["from"]
623 assert_equal JSON.dump(["+15550000000"]), event["to"]
624 assert_equal "bw-in-123", event["bandwidth_id"]
625 assert_equal "+15550000000", event["owner"]
626 assert_equal "Hello from outside", event["body"]
627 assert_equal JSON.dump([]), event["media_urls"]
628 end
629 em :test_inbound_sms_emits_to_stream
630
631 def test_inbound_mms_emits_to_stream_and_filters_smil
632 payload = {
633 "type" => "message-received",
634 "message" => {
635 "id" => "bw-mms-456",
636 "direction" => "in",
637 "owner" => "+15550000000",
638 "from" => "+15551234567",
639 "to" => ["+15550000000"],
640 "time" => "2025-01-13T10:05:00Z",
641 "text" => "Check this out",
642 "media" => [
643 "https://example.com/image.jpg",
644 "https://example.com/file.smil",
645 "https://example.com/data.txt",
646 "https://example.com/meta.xml"
647 ]
648 }
649 }
650
651 invoke_webhook(payload)
652
653 entries = REDIS.stream_entries("messages").sync
654 assert_equal 1, entries.length
655
656 event = entries.first[:fields]
657 assert_equal "in", event["event"]
658 assert_equal JSON.dump(["https://example.com/image.jpg"]), event["media_urls"]
659 end
660 em :test_inbound_mms_emits_to_stream_and_filters_smil
661
662 def test_message_delivered_emits_to_stream
663 payload = {
664 "type" => "message-delivered",
665 "message" => {
666 "id" => "bw-out-789",
667 "direction" => "out",
668 "owner" => "+15550000000",
669 "from" => "+15550000000",
670 "to" => ["+15551234567"],
671 "time" => "2025-01-13T10:10:00Z",
672 "tag" => "stanza-id-abc extra-data"
673 }
674 }
675
676 invoke_webhook(payload)
677
678 entries = REDIS.stream_entries("messages").sync
679 assert_equal 1, entries.length
680
681 event = entries.first[:fields]
682 assert_equal "delivered", event["event"]
683 assert_equal "stanza-id-abc", event["stanza_id"]
684 assert_equal "bw-out-789", event["bandwidth_id"]
685 assert_equal "2025-01-13T10:10:00Z", event["timestamp"]
686 end
687 em :test_message_delivered_emits_to_stream
688
689 def test_message_failed_emits_to_stream
690 payload = {
691 "type" => "message-failed",
692 "message" => {
693 "id" => "bw-out-999",
694 "direction" => "out",
695 "owner" => "+15550000000",
696 "from" => "+15550000000",
697 "to" => ["+15551234567"],
698 "time" => "2025-01-13T10:15:00Z",
699 "tag" => "failed-stanza-xyz extra",
700 "errorCode" => 4720,
701 "description" => "Carrier rejected message"
702 }
703 }
704
705 invoke_webhook(payload)
706
707 entries = REDIS.stream_entries("messages").sync
708 assert_equal 1, entries.length
709
710 event = entries.first[:fields]
711 assert_equal "failed", event["event"]
712 assert_equal "failed-stanza-xyz", event["stanza_id"]
713 assert_equal "bw-out-999", event["bandwidth_id"]
714 assert_equal "4720", event["error_code"]
715 assert_equal "Carrier rejected message", event["error_description"]
716 assert_equal "2025-01-13T10:15:00Z", event["timestamp"]
717 end
718 em :test_message_failed_emits_to_stream
719
720 def test_sentry_captures_handler_exception
721 captured_exceptions = []
722 repo = SGXbwmsgsv2.instance_variable_get(:@registration_repo)
723
724 Sentry.stub :capture_exception, ->(*args, **) { captured_exceptions << args.first } do
725 # repo.find is called during message processing to look up creds;
726 # raising here bubbles up to SGXClient#handle_error
727 repo.stub :find, ->(_) { raise StandardError, "test error" } do
728 m = Blather::Stanza::Message.new("+15551234567@component", "hello")
729 m.from = "test@example.com"
730 process_stanza(m)
731 end
732 end
733
734 assert_equal 1, captured_exceptions.length
735 assert_instance_of StandardError, captured_exceptions.first
736 assert_equal "test error", captured_exceptions.first.message
737
738 assert_equal 1, written.length
739 stanza = Blather::XMPPNode.parse(written.first.to_xml)
740 assert stanza.error?
741 error = stanza.find_first("error")
742 assert_equal "cancel", error["type"]
743 assert_equal "internal-server-error", xmpp_error_name(error)
744
745 SGXbwmsgsv2.instance_variable_set(:@written, [])
746 end
747 em :test_sentry_captures_handler_exception
748end