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