test_component.rb

  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%20extra-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%20extra",
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