1# frozen_string_literal: true
2
3require "rack/test"
4require "test_helper"
5require "bwmsgsv2_repo"
6require "customer_repo"
7require_relative "../web"
8
9ExpiringLock::REDIS = Minitest::Mock.new
10Customer::BLATHER = Minitest::Mock.new
11CustomerFwd::BANDWIDTH_VOICE = Minitest::Mock.new
12Web::BANDWIDTH_VOICE = Minitest::Mock.new
13LowBalance::AutoTopUp::Transaction = Minitest::Mock.new
14
15class WebTest < Minitest::Test
16 include Rack::Test::Methods
17
18 def app
19 Web.opts[:customer_repo] = CustomerRepo.new(
20 redis: FakeRedis.new(
21 "jmp_customer_jid-customerid" => "customer@example.com",
22 "catapult_jid-+15551234567" => "customer_customerid@component",
23 "jmp_customer_jid-customerid_low" => "customer@example.com",
24 "catapult_jid-+15551234560" => "customer_customerid_low@component",
25 "jmp_customer_jid-customerid_topup" => "customer@example.com",
26 "jmp_customer_auto_top_up_amount-customerid_topup" => "15",
27 "jmp_customer_monthly_overage_limit-customerid_topup" => "99999",
28 "catapult_jid-+15551234562" => "customer_customerid_topup@component",
29 "jmp_customer_jid-customerid_limit" => "customer@example.com",
30 "catapult_jid-+15551234561" => "customer_customerid_limit@component",
31 "jmp_customer_jid-customerid2" => "customer2@example.com",
32 "catapult_jid-+15551230000" => "customer_customerid2@component"
33 ),
34 db: FakeDB.new(
35 ["customerid"] => [{
36 "balance" => BigDecimal(10),
37 "plan_name" => "test_usd",
38 "expires_at" => Time.now + 100
39 }],
40 ["customerid2"] => [{
41 "balance" => BigDecimal(10),
42 "plan_name" => "test_usd",
43 "expires_at" => Time.now + 100
44 }],
45 ["customerid_low"] => [{
46 "balance" => BigDecimal("0.01"),
47 "plan_name" => "test_usd",
48 "expires_at" => Time.now + 100
49 }],
50 ["customerid_topup"] => [{
51 "balance" => BigDecimal("0.01"),
52 "plan_name" => "test_usd",
53 "expires_at" => Time.now + 100
54 }],
55 ["customerid_limit"] => [{
56 "balance" => BigDecimal(10),
57 "plan_name" => "test_usd",
58 "expires_at" => Time.now + 100
59 }]
60 ),
61 sgx_repo: Bwmsgsv2Repo.new(
62 redis: FakeRedis.new(
63 "catapult_fwd-+15551234567" => "xmpp:customer@example.com",
64 "catapult_fwd_timeout-customer_customerid@component" => "30",
65 "catapult_fwd-+15551234560" => "xmpp:customer@example.com",
66 "catapult_fwd_timeout-customer_customerid_low@component" => "30",
67 "catapult_fwd-+15551234561" => "xmpp:customer@example.com",
68 "catapult_fwd_timeout-customer_customerid_limit@component" => "30",
69 "catapult_fwd-+15551230000" => "xmpp:customer2@example.com",
70 "catapult_fwd_timeout-customer_customerid2@component" => "30"
71 ),
72 ibr_repo: FakeIBRRepo.new(
73 "sgx" => {
74 "customer_customerid@component" =>
75 Blather::Stanza::Iq::IBR.new.tap do |ibr|
76 ibr.phone = "+15551234567"
77 end,
78 "customer_customerid_low@component" =>
79 Blather::Stanza::Iq::IBR.new.tap do |ibr|
80 ibr.phone = "+15551234567"
81 end,
82 "customer_customerid_topup@component" =>
83 Blather::Stanza::Iq::IBR.new.tap do |ibr|
84 ibr.phone = "+15551234567"
85 end,
86 "customer_customerid_limit@component" =>
87 Blather::Stanza::Iq::IBR.new.tap do |ibr|
88 ibr.phone = "+15551234567"
89 end
90 }
91 )
92 )
93 )
94 Web.opts[:call_attempt_repo] = CallAttemptRepo.new(
95 redis: FakeRedis.new,
96 db: FakeDB.new(
97 ["test_usd", "+15557654321", :outbound] => [{ "rate" => 0.01 }],
98 ["test_usd", "+15557654321", :inbound] => [{ "rate" => 0.01 }],
99 ["customerid_limit"] => FakeDB::MultiResult.new(
100 [{ "a" => 1000 }],
101 [{ "settled_amount" => 15 }]
102 ),
103 ["customerid_low"] => FakeDB::MultiResult.new(
104 [{ "a" => 1000 }],
105 [{ "settled_amount" => 15 }]
106 ),
107 ["customerid_topup"] => FakeDB::MultiResult.new(
108 [{ "a" => 1000 }],
109 [{ "settled_amount" => 15 }]
110 )
111 )
112 )
113 Web.opts[:common_logger] = FakeLog.new
114 Web.instance_variable_set(:@outbound_transfers, { "bcall" => "oocall" })
115 Web.app
116 end
117
118 def test_outbound_forwards
119 post(
120 "/outbound/calls",
121 {
122 from: "customerid",
123 to: "+15557654321",
124 callId: "acall"
125 }.to_json,
126 { "CONTENT_TYPE" => "application/json" }
127 )
128
129 assert last_response.ok?
130 assert_equal(
131 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
132 "<Transfer transferCallerId=\"+15551234567\">" \
133 "<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
134 last_response.body
135 )
136 end
137 em :test_outbound_forwards
138
139 def test_outbound_low_balance
140 ExpiringLock::REDIS.expect(
141 :set,
142 EMPromise.resolve(nil),
143 ["jmp_customer_low_balance-customerid_low", Time, "EX", 604800, "NX"]
144 )
145
146 post(
147 "/outbound/calls",
148 {
149 from: "customerid_low",
150 to: "+15557654321",
151 callId: "acall"
152 }.to_json,
153 { "CONTENT_TYPE" => "application/json" }
154 )
155
156 assert last_response.ok?
157 assert_equal(
158 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
159 "<SpeakSentence>Your balance of $0.01 is not enough to " \
160 "complete this call.</SpeakSentence></Response>",
161 last_response.body
162 )
163 assert_mock ExpiringLock::REDIS
164 end
165 em :test_outbound_low_balance
166
167 def test_outbound_low_balance_top_up
168 LowBalance::AutoTopUp::Transaction.expect(
169 :sale,
170 EMPromise.resolve(
171 OpenStruct.new(insert: EMPromise.resolve(nil), total: 15)
172 ),
173 [Customer], amount: 15
174 )
175
176 ExpiringLock::REDIS.expect(
177 :set,
178 EMPromise.resolve("OK"),
179 ["jmp_customer_low_balance-customerid_topup", Time, "EX", 604800, "NX"]
180 )
181
182 CustomerFinancials::REDIS.expect(
183 :smembers,
184 EMPromise.resolve([]),
185 ["block_credit_cards"]
186 )
187 LowBalance::AutoTopUp::REDIS.expect(
188 :exists,
189 0,
190 ["jmp_auto_top_up_block-abcd"]
191 )
192 braintree_customer = Minitest::Mock.new
193 CustomerFinancials::BRAINTREE.expect(:customer, braintree_customer)
194 payment_methods = OpenStruct.new(payment_methods: [
195 OpenStruct.new(default?: true, unique_number_identifier: "abcd")
196 ])
197 braintree_customer.expect(
198 :find,
199 EMPromise.resolve(payment_methods),
200 ["customerid_topup"]
201 )
202
203 Customer::BLATHER.expect(
204 :<<,
205 nil,
206 [Blather::Stanza]
207 )
208
209 post(
210 "/outbound/calls",
211 {
212 from: "customerid_topup",
213 to: "+15557654321",
214 callId: "acall"
215 }.to_json,
216 { "CONTENT_TYPE" => "application/json" }
217 )
218
219 assert last_response.ok?
220 assert_equal(
221 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
222 "<Transfer transferCallerId=\"+15551234567\">" \
223 "<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
224 last_response.body
225 )
226 assert_mock ExpiringLock::REDIS
227 assert_mock Customer::BLATHER
228 assert_mock LowBalance::AutoTopUp::Transaction
229 end
230 em :test_outbound_low_balance_top_up
231
232 def test_outbound_unsupported
233 post(
234 "/outbound/calls",
235 {
236 from: "customerid_limit",
237 to: "+95557654321",
238 callId: "acall"
239 }.to_json,
240 { "CONTENT_TYPE" => "application/json" }
241 )
242
243 assert last_response.ok?
244 assert_equal(
245 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
246 "<SpeakSentence>The number you have dialled is not " \
247 "supported on your account.</SpeakSentence></Response>",
248 last_response.body
249 )
250 end
251 em :test_outbound_unsupported
252
253 def test_outbound_atlimit
254 post(
255 "/outbound/calls",
256 {
257 from: "customerid_limit",
258 to: "+15557654321",
259 callId: "acall"
260 }.to_json,
261 { "CONTENT_TYPE" => "application/json" }
262 )
263
264 assert last_response.ok?
265 assert_equal(
266 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
267 "<Gather gatherUrl=\"\/outbound/calls\" maxDigits=\"1\" " \
268 "repeatCount=\"3\"><SpeakSentence>This call will take you over " \
269 "your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
270 "Change your limit in your account settings or press 1 to accept the " \
271 "charges. You can hang up to cancel.</SpeakSentence></Gather></Response>",
272 last_response.body
273 )
274 end
275 em :test_outbound_atlimit
276
277 def test_outbound_no_customer
278 post(
279 "/outbound/calls",
280 {
281 from: "no_such_customer",
282 to: "+15557654321",
283 callId: "acall"
284 }.to_json,
285 { "CONTENT_TYPE" => "application/json" }
286 )
287
288 assert last_response.ok?
289 assert_equal(
290 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
291 "<SpeakSentence>Your credentials are invalid, please contact support." \
292 "</SpeakSentence></Response>",
293 last_response.body
294 )
295 end
296 em :test_outbound_no_customer
297
298 def test_outbound_atlimit_digits
299 post(
300 "/outbound/calls",
301 {
302 from: "customerid_limit",
303 to: "+15557654321",
304 callId: "acall",
305 digits: "1"
306 }.to_json,
307 { "CONTENT_TYPE" => "application/json" }
308 )
309
310 assert last_response.ok?
311 assert_equal(
312 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
313 "<Transfer transferCallerId=\"+15551234567\">" \
314 "<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
315 last_response.body
316 )
317 end
318 em :test_outbound_atlimit_digits
319
320 def test_inbound
321 CustomerFwd::BANDWIDTH_VOICE.expect(
322 :create_call,
323 OpenStruct.new(data: OpenStruct.new(call_id: "ocall")),
324 ["test_bw_account"],
325 body: Matching.new do |arg|
326 assert_equal(
327 "http://example.org/inbound/calls/acall?customer_id=customerid",
328 arg.answer_url
329 )
330 end
331 )
332
333 post(
334 "/inbound/calls",
335 {
336 from: "+15557654321",
337 to: "+15551234567",
338 callId: "acall"
339 }.to_json,
340 { "CONTENT_TYPE" => "application/json" }
341 )
342
343 assert last_response.ok?
344 assert_equal(
345 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
346 "<Ring answerCall=\"false\" duration=\"300\" />" \
347 "</Response>",
348 last_response.body
349 )
350 assert_mock CustomerFwd::BANDWIDTH_VOICE
351 end
352 em :test_inbound
353
354 def test_inbound_no_bwmsgsv2
355 CustomerFwd::BANDWIDTH_VOICE.expect(
356 :create_call,
357 OpenStruct.new(data: OpenStruct.new(call_id: "ocall")),
358 ["test_bw_account"],
359 body: Matching.new do |arg|
360 assert_equal(
361 "http://example.org/inbound/calls/acall?customer_id=customerid2",
362 arg.answer_url
363 )
364 end
365 )
366
367 post(
368 "/inbound/calls",
369 {
370 from: "+15557654321",
371 to: "+15551230000",
372 callId: "acall"
373 }.to_json,
374 { "CONTENT_TYPE" => "application/json" }
375 )
376
377 assert last_response.ok?
378 assert_equal(
379 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
380 "<Ring answerCall=\"false\" duration=\"300\" />" \
381 "</Response>",
382 last_response.body
383 )
384 assert_mock CustomerFwd::BANDWIDTH_VOICE
385 end
386 em :test_inbound_no_bwmsgsv2
387
388 def test_inbound_low
389 ExpiringLock::REDIS.expect(
390 :set,
391 EMPromise.resolve(nil),
392 ["jmp_customer_low_balance-customerid_low", Time, "EX", 604800, "NX"]
393 )
394
395 post(
396 "/inbound/calls",
397 {
398 from: "+15557654321",
399 to: "+15551234560",
400 callId: "acall"
401 }.to_json,
402 { "CONTENT_TYPE" => "application/json" }
403 )
404
405 assert last_response.ok?
406 assert_equal(
407 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
408 "<Redirect redirectUrl=\"/inbound/calls/acall/voicemail\" />" \
409 "</Response>",
410 last_response.body
411 )
412 assert_mock CustomerFwd::BANDWIDTH_VOICE
413 assert_mock ExpiringLock::REDIS
414 end
415 em :test_inbound_low
416
417 def test_inbound_leg2
418 post(
419 "/inbound/calls/acall?customer_id=customerid",
420 {
421 from: "+15557654321",
422 to: "sip:boop@example.com",
423 callId: "ocall"
424 }.to_json,
425 { "CONTENT_TYPE" => "application/json" }
426 )
427
428 assert last_response.ok?
429 assert_equal(
430 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
431 "<Tag>connected</Tag><Bridge>acall</Bridge>" \
432 "</Response>",
433 last_response.body
434 )
435 end
436 em :test_inbound_leg2
437
438 def test_inbound_limit_leg2
439 path = "/inbound/calls/acall?customer_id=customerid_limit"
440
441 post(
442 path,
443 {
444 from: "+15557654321",
445 to: "sip:boop@example.com",
446 callId: "ocall"
447 }.to_json,
448 { "CONTENT_TYPE" => "application/json" }
449 )
450
451 assert last_response.ok?
452 assert_equal(
453 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
454 "<Gather gatherUrl=\"#{path}\" maxDigits=\"1\" " \
455 "repeatCount=\"3\"><SpeakSentence>This call will take you over " \
456 "your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
457 "Change your limit in your account settings or press 1 to accept the " \
458 "charges. You can hang up to send the caller to voicemail." \
459 "</SpeakSentence></Gather></Response>",
460 last_response.body
461 )
462 end
463 em :test_inbound_limit_leg2
464
465 def test_inbound_limit_digits_leg2
466 post(
467 "/inbound/calls/acall?customer_id=customerid_limit",
468 {
469 from: "+15557654321",
470 to: "sip:boop@example.com",
471 callId: "ocall",
472 digits: "1"
473 }.to_json,
474 { "CONTENT_TYPE" => "application/json" }
475 )
476
477 assert last_response.ok?
478 assert_equal(
479 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
480 "<Tag>connected</Tag><Bridge>acall</Bridge>" \
481 "</Response>",
482 last_response.body
483 )
484 end
485 em :test_inbound_limit_digits_leg2
486
487 def test_inbound_limit_hangup
488 Web::BANDWIDTH_VOICE.expect(
489 :modify_call,
490 nil,
491 [
492 "test_bw_account",
493 "bcall"
494 ],
495 body: Matching.new do |arg|
496 assert_equal(
497 "http://example.org/inbound/calls/oocall/voicemail",
498 arg.redirect_url
499 )
500 end
501 )
502
503 post(
504 "/inbound/calls/bcall/transfer_complete",
505 {
506 from: "+15557654321",
507 to: "+15551234561",
508 callId: "oocall",
509 cause: "hangup"
510 }.to_json,
511 { "CONTENT_TYPE" => "application/json" }
512 )
513
514 assert last_response.ok?
515 assert_mock Web::BANDWIDTH_VOICE
516 end
517 em :test_inbound_limit_hangup
518
519 def test_voicemail
520 language_id = stub_request(:post, "https://api.rev.ai/languageid/v1/jobs")
521 .with(body: {
522 metadata: {
523 media_url: "https://jmp.chat/media",
524 from_jid: "+15557654321@component",
525 customer_id: "customerid"
526 }.to_json,
527 source_config: {
528 url: "https://jmp.chat/media"
529 },
530 notification_config: {
531 url: "http://example.org/inbound/calls/CALLID/voicemail/language_id"
532 }
533 }.to_json)
534
535 Customer::BLATHER.expect(
536 :<<,
537 nil,
538 [Matching.new do |stanza|
539 assert_equal "+15557654321@component", stanza.from.to_s
540 assert_equal "customer@example.com", stanza.to.to_s
541 assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
542 end]
543 )
544
545 post(
546 "/inbound/calls/CALLID/voicemail/audio",
547 {
548 "startTime" => "2021-01-01T00:00:00Z",
549 "endTime" => "2021-01-01T00:00:06Z",
550 "mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
551 "to" => "+15551234567",
552 "from" => "+15557654321"
553 }.to_json,
554 { "CONTENT_TYPE" => "application/json" }
555 )
556
557 assert last_response.ok?
558 assert_mock Customer::BLATHER
559 assert_requested language_id
560 end
561 em :test_voicemail
562
563 def test_anonymous_voicemail
564 language_id = stub_request(:post, "https://api.rev.ai/languageid/v1/jobs")
565 .with(body: {
566 metadata: {
567 media_url: "https://jmp.chat/media",
568 from_jid:
569 "16;phone-context=anonymous.phone-context.soprani.ca@component",
570 customer_id: "customerid"
571 }.to_json,
572 source_config: {
573 url: "https://jmp.chat/media"
574 },
575 notification_config: {
576 url: "http://example.org/inbound/calls/CALLID/voicemail/language_id"
577 }
578 }.to_json)
579
580 Customer::BLATHER.expect(
581 :<<,
582 nil,
583 [Matching.new do |stanza|
584 assert_equal(
585 "16;phone-context=anonymous.phone-context.soprani.ca@component",
586 stanza.from.to_s
587 )
588 assert_equal "customer@example.com", stanza.to.to_s
589 assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
590 end]
591 )
592
593 post(
594 "/inbound/calls/CALLID/voicemail/audio",
595 {
596 "startTime" => "2021-01-01T00:00:00Z",
597 "endTime" => "2021-01-01T00:00:06Z",
598 "mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
599 "to" => "+15551234567",
600 "from" => "Anonymous"
601 }.to_json,
602 { "CONTENT_TYPE" => "application/json" }
603 )
604
605 assert last_response.ok?
606 assert_mock Customer::BLATHER
607 assert_requested language_id
608 end
609 em :test_anonymous_voicemail
610
611 def test_voicemail_short
612 post(
613 "/inbound/calls/CALLID/voicemail/audio",
614 {
615 "startTime" => "2021-01-01T00:00:00Z",
616 "endTime" => "2021-01-01T00:00:05Z"
617 }.to_json,
618 { "CONTENT_TYPE" => "application/json" }
619 )
620
621 assert last_response.ok?
622 assert_mock Customer::BLATHER
623 end
624 em :test_voicemail_short
625
626 def test_voicemail_no_customer
627 post(
628 "/inbound/calls/CALLID/voicemail",
629 {
630 "startTime" => "2021-01-01T00:00:00Z",
631 "endTime" => "2021-01-01T00:00:06Z",
632 "mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
633 "to" => "+15551200000",
634 "from" => "+15557654321"
635 }.to_json,
636 { "CONTENT_TYPE" => "application/json" }
637 )
638
639 assert last_response.ok?
640 assert_equal(
641 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
642 "<SpeakSentence>The number you have dialled is not in service." \
643 "</SpeakSentence></Response>",
644 last_response.body
645 )
646 end
647 em :test_voicemail_no_customer
648end