1# frozen_string_literal: true
2
3require "test_helper"
4
5def panic(e)
6 $panic = e
7end
8
9class ComponentTest < Minitest::Test
10 def setup
11 reset_stanzas!
12 reset_redis!
13 FakeFS.clear!
14 end
15
16 def test_message_unregistered
17 m = Blather::Stanza::Message.new("+15551234567@component", "a"*4096)
18 m.from = "unknown@example.com"
19 process_stanza(m)
20
21 assert_equal 1, written.length
22
23 stanza = Blather::XMPPNode.parse(written.first.to_xml)
24 assert stanza.error?
25 error = stanza.find_first("error")
26 assert_equal "auth", error["type"]
27 assert_equal "registration-required", xmpp_error_name(error)
28 end
29 em :test_message_unregistered
30
31 def test_message_too_long
32 body = "a" * 4096
33 file = Multibases.pack(
34 'base58btc',
35 Multihashes.encode(Digest::SHA256.digest(body), "sha2-256")
36 ).to_s
37 expected_url = "https://mms.test.example.com/#{file}.txt"
38
39 req = stub_request(
40 :post,
41 "https://messaging.bandwidth.com/api/v2/users/account/messages"
42 ).with(body: {
43 from: "+15550000000",
44 to: "+15551234567",
45 text: "",
46 media: expected_url,
47 applicationId: nil,
48 tag: " "
49 }).to_return(status: 201, body: JSON.dump(id: "bw-mms-123"))
50
51 m = Blather::Stanza::Message.new("+15551234567@component", body)
52 m.from = "test@example.com"
53 process_stanza(m)
54
55 assert_requested req
56 assert_empty written
57 assert_equal body, File.read("/mms/#{file}")
58 end
59 em :test_message_too_long
60
61 def test_message_to_component_not_group
62 m = Blather::Stanza::Message.new("component", "a"*4096)
63 m.from = "test@example.com"
64 process_stanza(m)
65
66 assert_equal 1, written.length
67
68 stanza = Blather::XMPPNode.parse(written.first.to_xml)
69 assert stanza.error?
70 error = stanza.find_first("error")
71 assert_equal "cancel", error["type"]
72 assert_equal "item-not-found", xmpp_error_name(error)
73 end
74 em :test_message_to_component_not_group
75
76 def test_message_to_invalid_num
77 m = Blather::Stanza::Message.new("123@component", "a"*4096)
78 m.from = "test@example.com"
79 process_stanza(m)
80
81 assert_equal 1, written.length
82
83 stanza = Blather::XMPPNode.parse(written.first.to_xml)
84 assert stanza.error?
85 error = stanza.find_first("error")
86 assert_equal "cancel", error["type"]
87 assert_equal "item-not-found", xmpp_error_name(error)
88 end
89 em :test_message_to_invalid_num
90
91 def test_message_to_anonymous
92 m = Blather::Stanza::Message.new(
93 "1;phone-context=anonymous.phone-context.soprani.ca@component",
94 "a"*4096
95 )
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 "gone", xmpp_error_name(error)
106 end
107 em :test_message_to_anonymous
108
109 def test_blank_message
110 m = Blather::Stanza::Message.new("+15551234567@component", " ")
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 "modify", error["type"]
120 assert_equal "policy-violation", xmpp_error_name(error)
121 end
122 em :test_blank_message
123
124 def test_ibr_bad_tel
125 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
126 iq.from = "newuser@example.com"
127 iq.phone = "5551234567"
128 process_stanza(iq)
129
130 assert_equal 1, written.length
131
132 stanza = Blather::XMPPNode.parse(written.first.to_xml)
133 assert stanza.error?
134 error = stanza.find_first("error")
135 assert_equal "cancel", error["type"]
136 assert_equal "item-not-found", xmpp_error_name(error)
137 end
138 em :test_ibr_bad_tel
139
140 def test_ibr_bad_creds
141 stub_request(
142 :get,
143 "https://messaging.bandwidth.com/api/v2/users/acct/media"
144 ).with(basic_auth: ["user", "pw"]).to_return(status: 401)
145
146 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
147 iq.from = "newuser@example.com"
148 iq.phone = "+15551234567"
149 iq.nick = "acct"
150 iq.username = "user"
151 iq.password = "pw"
152 process_stanza(iq)
153
154 assert_equal 1, written.length
155
156 stanza = Blather::XMPPNode.parse(written.first.to_xml)
157 assert stanza.error?
158 error = stanza.find_first("error")
159 assert_equal "auth", error["type"]
160 assert_equal "not-authorized", xmpp_error_name(error)
161 end
162 em :test_ibr_bad_creds
163
164 def test_ibr_number_not_found
165 stub_request(
166 :get,
167 "https://messaging.bandwidth.com/api/v2/users/acct/media"
168 ).with(basic_auth: ["user", "pw"]).to_return(status: 404)
169
170 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
171 iq.from = "newuser@example.com"
172 iq.phone = "+15551234567"
173 iq.nick = "acct"
174 iq.username = "user"
175 iq.password = "pw"
176 process_stanza(iq)
177
178 assert_equal 1, written.length
179
180 stanza = Blather::XMPPNode.parse(written.first.to_xml)
181 assert stanza.error?
182 error = stanza.find_first("error")
183 assert_equal "cancel", error["type"]
184 assert_equal "item-not-found", xmpp_error_name(error)
185 end
186 em :test_ibr_number_not_found
187
188 def test_ibr_other_error
189 stub_request(
190 :get,
191 "https://messaging.bandwidth.com/api/v2/users/acct/media"
192 ).with(basic_auth: ["user", "pw"]).to_return(status: 400)
193
194 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
195 iq.from = "newuser@example.com"
196 iq.phone = "+15551234567"
197 iq.nick = "acct"
198 iq.username = "user"
199 iq.password = "pw"
200 process_stanza(iq)
201
202 assert_equal 1, written.length
203
204 stanza = Blather::XMPPNode.parse(written.first.to_xml)
205 assert stanza.error?
206 error = stanza.find_first("error")
207 assert_equal "modify", error["type"]
208 assert_equal "not-acceptable", xmpp_error_name(error)
209 end
210 em :test_ibr_other_error
211
212 def test_ibr_new
213 stub_request(
214 :get,
215 "https://messaging.bandwidth.com/api/v2/users/acct/media"
216 ).with(basic_auth: ["user", "pw"]).to_return(status: 200, body: "[]")
217
218 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
219 iq.from = "test9@example.com"
220 iq.phone = "+15550000009"
221 iq.nick = "acct"
222 iq.username = "user"
223 iq.password = "pw"
224 process_stanza(iq)
225
226 assert_equal 1, written.length
227
228 stanza = Blather::XMPPNode.parse(written.first.to_xml)
229 refute stanza.error?
230 assert_equal(
231 ["acct", "user", "pw", "+15550000009"],
232 REDIS.get("catapult_cred-test9@example.com").sync
233 )
234 assert_equal "test9@example.com", REDIS.get("catapult_jid-+15550000009").sync
235 assert REDIS.get("catapult_jid-").sync
236 end
237 em :test_ibr_new
238
239 def test_ibr_update
240 stub_request(
241 :get,
242 "https://messaging.bandwidth.com/api/v2/users/acct/media"
243 ).with(basic_auth: ["user", "pw"]).to_return(status: 200, body: "[]")
244
245 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
246 iq.from = "test@example.com"
247 iq.phone = "+15550000009"
248 iq.nick = "acct"
249 iq.username = "user"
250 iq.password = "pw"
251 process_stanza(iq)
252
253 assert_equal 1, written.length
254
255 stanza = Blather::XMPPNode.parse(written.first.to_xml)
256 refute stanza.error?
257 assert_equal(
258 ["acct", "user", "pw", "+15550000009"],
259 REDIS.get("catapult_cred-test@example.com").sync
260 )
261 assert_equal "test@example.com", REDIS.get("catapult_jid-+15550000009").sync
262 refute REDIS.get("catapult_jid-+15550000000").sync
263 end
264 em :test_ibr_update
265
266 def test_ibr_conflict
267 stub_request(
268 :get,
269 "https://messaging.bandwidth.com/api/v2/users/acct/media"
270 ).with(basic_auth: ["user", "pw"]).to_return(status: 200, body: "[]")
271
272 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
273 iq.from = "test2@example.com"
274 iq.phone = "+15550000000"
275 iq.nick = "acct"
276 iq.username = "user"
277 iq.password = "pw"
278 process_stanza(iq)
279
280 assert_equal 1, written.length
281
282 stanza = Blather::XMPPNode.parse(written.first.to_xml)
283 assert stanza.error?
284 error = stanza.find_first("error")
285 assert_equal "cancel", error["type"]
286 assert_equal "conflict", xmpp_error_name(error)
287 assert_equal(
288 "Another user exists for +15550000000",
289 xmpp_error_text(error)
290 )
291 end
292 em :test_ibr_conflict
293
294 def test_ibr_remove
295 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
296 iq.from = "test@example.com"
297 iq.remove!
298 process_stanza(iq)
299
300 refute REDIS.get("catapult_cred-test@example.com").sync
301
302 assert_equal 1, written.length
303
304 stanza = Blather::XMPPNode.parse(written.first.to_xml)
305 assert stanza.result?
306 end
307 em :test_ibr_remove
308
309 def test_ibr_form
310 stub_request(
311 :get,
312 "https://messaging.bandwidth.com/api/v2/users/acct/media"
313 ).with(basic_auth: ["user", "pw"]).to_return(status: 200, body: "[]")
314
315 iq = Blather::Stanza::Iq::IBR.new(:set, "component")
316 iq.from = "formuser@example.com"
317 form = Blather::Stanza::X.find_or_create(iq.query)
318 form.fields = [
319 {
320 var: "nick",
321 value: "acct"
322 },
323 {
324 var: "username",
325 value: "user"
326 },
327 {
328 var: "password",
329 value: "pw"
330 },
331 {
332 var: "phone",
333 value: "+15551234567"
334 }
335 ]
336 process_stanza(iq)
337
338 assert_equal(
339 ["acct", "user", "pw", "+15551234567"],
340 REDIS.get("catapult_cred-formuser@example.com").sync
341 )
342
343 assert_equal(
344 "formuser@example.com",
345 REDIS.get("catapult_jid-+15551234567").sync
346 )
347
348 assert_equal 1, written.length
349 stanza = Blather::XMPPNode.parse(written.first.to_xml)
350 assert stanza.result?
351 end
352 em :test_ibr_form
353
354 def test_ibr_get_form_registered
355 iq = Blather::Stanza::Iq::IBR.new(:get, "component")
356 iq.from = "test@example.com"
357 process_stanza(iq)
358
359 assert_equal 1, written.length
360 stanza = Blather::XMPPNode.parse(written.first.to_xml)
361 assert stanza.result?
362 assert stanza.registered?
363 assert_equal(
364 ["nick", "username", "password", "phone"],
365 stanza.form.fields.map(&:var)
366 )
367 assert stanza.instructions
368 assert stanza.nick
369 assert stanza.username
370 assert stanza.password
371 assert stanza.phone
372 refute stanza.email
373 end
374 em :test_ibr_get_form_registered
375
376 def test_port_out_pin
377 iq = Blather::Stanza::Iq::Command.new(:set, 'component').tap do |iq|
378 iq.from = 'test@example.com'
379 iq.node = 'set-port-out-pin'
380 iq.sessionid = 'test-session-123'
381 iq.action = :complete
382 iq.form.type = :submit
383 iq.form.fields = [
384 {
385 var: 'pin',
386 value: '1234'
387 },
388 {
389 var: 'confirm_pin',
390 value: '1234'
391 }
392 ]
393 end
394
395 tn_mock = Minitest::Mock.new
396 tn_mock.expect(:get_details, { tier: 0.0, on_net_vendor: true })
397 with_stubs([
398 [
399 BandwidthIris::TnOptions,
400 :create_tn_option_order,
401 ->(client, data) { {order_id: 'test-order-123', processing_status: 'RECEIVED', error_list: {}} }
402 ],
403 [
404 BandwidthIris::TnOptions,
405 :get_tn_option_order,
406 ->(client, order_id) { {order_id: order_id, order_status: 'COMPLETE', error_list: {}} }
407 ],
408 [
409 BandwidthIris::Tn,
410 :get,
411 tn_mock
412 ]
413 ]) do
414 process_stanza(iq)
415
416 assert_equal 1, written.length
417
418 stanza = Blather::XMPPNode.parse(written.first.to_xml)
419 refute stanza.error?
420 assert_mock tn_mock
421 end
422 end
423 em :test_port_out_pin
424
425 def test_port_out_pin_mismatch
426 iq = Blather::Stanza::Iq::Command.new(:set, 'component').tap do |iq|
427 iq.from = 'test@example.com'
428 iq.node = 'set-port-out-pin'
429 iq.sessionid = 'test-session-mismatch'
430 iq.action = :complete
431 iq.form.type = :submit
432 iq.form.fields = [
433 {
434 var: 'pin',
435 value: '1234'
436 },
437 {
438 var: 'confirm_pin',
439 value: '5678'
440 }
441 ]
442 end
443
444 process_stanza(iq)
445
446 assert_equal 1, written.length
447
448 stanza = Blather::XMPPNode.parse(written.first.to_xml)
449 assert_equal :error, stanza.type
450 error = stanza.find_first("error")
451 assert_equal "modify", error["type"]
452 assert_equal "bad-request", xmpp_error_name(error)
453 assert_equal "PIN confirmation does not match", xmpp_error_text(error)
454 end
455 em :test_port_out_pin_mismatch
456
457 def test_port_out_pin_validation
458 [
459 ['123', 'PIN must be 4-10 alphanumeric characters'],
460 ['12345678901', 'PIN must be 4-10 alphanumeric characters'],
461 ['123!', 'PIN must be 4-10 alphanumeric characters'],
462 ['pin with spaces', 'PIN must be 4-10 alphanumeric characters']
463 ].each do |invalid_pin, expected_error|
464 iq = Blather::Stanza::Iq::Command.new(:set, 'component').tap do |iq|
465 iq.from = 'test@example.com'
466 iq.node = 'set-port-out-pin'
467 iq.sessionid = "test-session-validation-#{invalid_pin.gsub(/[^a-zA-Z0-9]/, '')}"
468 iq.action = :complete
469 iq.form.type = :submit
470 iq.form.fields = [
471 {
472 var: 'pin',
473 value: invalid_pin
474 },
475 {
476 var: 'confirm_pin',
477 value: invalid_pin
478 }
479 ]
480 end
481
482 process_stanza(iq)
483
484 assert_equal 1, written.length, "Failed for PIN: #{invalid_pin}"
485
486 stanza = Blather::XMPPNode.parse(written.first.to_xml)
487 assert_equal :error, stanza.type, "Expected error for PIN: #{invalid_pin}"
488 error = stanza.find_first("error")
489 assert_equal "modify", error["type"]
490 assert_equal "bad-request", xmpp_error_name(error)
491 assert_equal expected_error, xmpp_error_text(error),
492 "Wrong error message for PIN: #{invalid_pin}"
493
494 SGXbwmsgsv2.instance_variable_set(:@written, [])
495 end
496 end
497 em :test_port_out_pin_validation
498
499 def test_outbound_message_emits_to_stream
500 stub_request(
501 :post,
502 "https://messaging.bandwidth.com/api/v2/users/account/messages"
503 ).with(body: hash_including(
504 from: "+15550000000",
505 to: "+15551234567",
506 text: "Hello world"
507 )).to_return(status: 201, body: JSON.dump(id: "bw-msg-123"))
508
509 m = Blather::Stanza::Message.new("+15551234567@component", "Hello world")
510 m.from = "test@example.com/resource"
511 m['id'] = "stanza-123"
512 process_stanza(m)
513
514 entries = REDIS.stream_entries("messages").sync
515 assert_equal 1, entries.length
516
517 event = entries.first[:fields]
518 assert_equal "out", event["event"]
519 assert_equal "+15550000000", event["from"]
520 assert_equal "+15550000000", event["owner"]
521 assert_equal JSON.dump(["+15551234567"]), event["to"]
522 assert_equal "stanza-123", event["stanza_id"]
523 assert_equal "bw-msg-123", event["bandwidth_id"]
524 assert_equal "Hello world", event["body"]
525 end
526 em :test_outbound_message_emits_to_stream
527
528 def test_passthrough_message_emits_to_stream
529 REDIS.set("catapult_jid-+15559999999", "other@example.com")
530 REDIS.set("catapult_cred-other@example.com", [
531 'other_acct', 'other_user', 'other_pw', '+15559999999'
532 ])
533
534 m = Blather::Stanza::Message.new("+15559999999@component", "Pass through")
535 m.from = "test@example.com/resource"
536 m['id'] = "passthru-stanza-456"
537 process_stanza(m)
538
539 entries = REDIS.stream_entries("messages").sync
540 assert_equal 1, entries.length
541
542 event = entries.first[:fields]
543 assert_equal "thru", event["event"]
544 assert_equal "+15550000000", event["from"]
545 assert_equal JSON.dump(["+15559999999"]), event["to"]
546 assert_equal "passthru-stanza-456", event["stanza_id"]
547 assert_equal "Pass through", event["body"]
548 refute event.key?("timestamp"), "Thru events should not have a timestamp field"
549 end
550 em :test_passthrough_message_emits_to_stream
551
552 def test_sentry_captures_handler_exception
553 captured_exceptions = []
554 repo = SGXbwmsgsv2.instance_variable_get(:@registration_repo)
555
556 Sentry.stub :capture_exception, ->(*args, **) { captured_exceptions << args.first } do
557 # repo.find is called during message processing to look up creds;
558 # raising here bubbles up to SGXClient#handle_error
559 repo.stub :find, ->(_) { raise StandardError, "test error" } do
560 m = Blather::Stanza::Message.new("+15551234567@component", "hello")
561 m.from = "test@example.com"
562 process_stanza(m)
563 end
564 end
565
566 assert_equal 1, captured_exceptions.length
567 assert_instance_of StandardError, captured_exceptions.first
568 assert_equal "test error", captured_exceptions.first.message
569
570 assert_equal 1, written.length
571 stanza = Blather::XMPPNode.parse(written.first.to_xml)
572 assert stanza.error?
573 error = stanza.find_first("error")
574 assert_equal "cancel", error["type"]
575 assert_equal "internal-server-error", xmpp_error_name(error)
576
577 SGXbwmsgsv2.instance_variable_set(:@written, [])
578 end
579 em :test_sentry_captures_handler_exception
580end