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