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" => IBR.new.tap do |ibr|
66 ibr.phone = "+15551234567"
67 end,
68 "customer_customerid_low@component" => IBR.new.tap do |ibr|
69 ibr.phone = "+15551234567"
70 end,
71 "customer_customerid_topup@component" => IBR.new.tap do |ibr|
72 ibr.phone = "+15551234567"
73 end,
74 "customer_customerid_limit@component" => IBR.new.tap do |ibr|
75 ibr.phone = "+15551234567"
76 end
77 }
78 )
79 )
80 )
81 Web.opts[:call_attempt_repo] = CallAttemptRepo.new(
82 redis: FakeRedis.new,
83 db: FakeDB.new(
84 ["test_usd", "+15557654321", :outbound] => [{ "rate" => 0.01 }],
85 ["test_usd", "+15557654321", :inbound] => [{ "rate" => 0.01 }],
86 ["customerid_limit"] => FakeDB::MultiResult.new(
87 [{ "a" => 1000 }],
88 [{ "settled_amount" => 15 }]
89 ),
90 ["customerid_low"] => FakeDB::MultiResult.new(
91 [{ "a" => 1000 }],
92 [{ "settled_amount" => 15 }]
93 ),
94 ["customerid_topup"] => FakeDB::MultiResult.new(
95 [{ "a" => 1000 }],
96 [{ "settled_amount" => 15 }]
97 )
98 )
99 )
100 Web.opts[:common_logger] = FakeLog.new
101 Web.instance_variable_set(:@outbound_transfers, { "bcall" => "oocall" })
102 Web.app
103 end
104
105 def test_outbound_forwards
106 post(
107 "/outbound/calls",
108 {
109 from: "customerid",
110 to: "+15557654321",
111 callId: "acall"
112 }.to_json,
113 { "CONTENT_TYPE" => "application/json" }
114 )
115
116 assert last_response.ok?
117 assert_equal(
118 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
119 "<Transfer transferCallerId=\"+15551234567\">" \
120 "<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
121 last_response.body
122 )
123 end
124 em :test_outbound_forwards
125
126 def test_outbound_low_balance
127 ExpiringLock::REDIS.expect(
128 :exists,
129 EMPromise.resolve(1),
130 ["jmp_customer_low_balance-customerid_low"]
131 )
132
133 post(
134 "/outbound/calls",
135 {
136 from: "customerid_low",
137 to: "+15557654321",
138 callId: "acall"
139 }.to_json,
140 { "CONTENT_TYPE" => "application/json" }
141 )
142
143 assert last_response.ok?
144 assert_equal(
145 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
146 "<SpeakSentence>Your balance of $0.01 is not enough to " \
147 "complete this call.</SpeakSentence></Response>",
148 last_response.body
149 )
150 assert_mock ExpiringLock::REDIS
151 end
152 em :test_outbound_low_balance
153
154 def test_outbound_low_balance_top_up
155 LowBalance::AutoTopUp::Transaction.expect(
156 :sale,
157 EMPromise.resolve(
158 OpenStruct.new(insert: EMPromise.resolve(nil), total: 15)
159 ),
160 [Customer, { amount: 15 }]
161 )
162
163 ExpiringLock::REDIS.expect(
164 :exists,
165 nil,
166 ["jmp_customer_low_balance-customerid_topup"]
167 )
168
169 ExpiringLock::REDIS.expect(
170 :setex,
171 nil,
172 ["jmp_customer_low_balance-customerid_topup", Integer, Time]
173 )
174
175 Customer::BLATHER.expect(
176 :<<,
177 nil,
178 [Blather::Stanza]
179 )
180
181 post(
182 "/outbound/calls",
183 {
184 from: "customerid_topup",
185 to: "+15557654321",
186 callId: "acall"
187 }.to_json,
188 { "CONTENT_TYPE" => "application/json" }
189 )
190
191 assert last_response.ok?
192 assert_equal(
193 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
194 "<Transfer transferCallerId=\"+15551234567\">" \
195 "<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
196 last_response.body
197 )
198 assert_mock ExpiringLock::REDIS
199 assert_mock Customer::BLATHER
200 assert_mock LowBalance::AutoTopUp::Transaction
201 end
202 em :test_outbound_low_balance_top_up
203
204 def test_outbound_unsupported
205 post(
206 "/outbound/calls",
207 {
208 from: "customerid_limit",
209 to: "+95557654321",
210 callId: "acall"
211 }.to_json,
212 { "CONTENT_TYPE" => "application/json" }
213 )
214
215 assert last_response.ok?
216 assert_equal(
217 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
218 "<SpeakSentence>The number you have dialled is not " \
219 "supported on your account.</SpeakSentence></Response>",
220 last_response.body
221 )
222 end
223 em :test_outbound_unsupported
224
225 def test_outbound_atlimit
226 post(
227 "/outbound/calls",
228 {
229 from: "customerid_limit",
230 to: "+15557654321",
231 callId: "acall"
232 }.to_json,
233 { "CONTENT_TYPE" => "application/json" }
234 )
235
236 assert last_response.ok?
237 assert_equal(
238 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
239 "<Gather gatherUrl=\"\/outbound/calls\" maxDigits=\"1\" " \
240 "repeatCount=\"3\"><SpeakSentence>This call will take you over " \
241 "your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
242 "Change your limit in your account settings or press 1 to accept the " \
243 "charges. You can hang up to cancel.</SpeakSentence></Gather></Response>",
244 last_response.body
245 )
246 end
247 em :test_outbound_atlimit
248
249 def test_outbound_atlimit_digits
250 post(
251 "/outbound/calls",
252 {
253 from: "customerid_limit",
254 to: "+15557654321",
255 callId: "acall",
256 digits: "1"
257 }.to_json,
258 { "CONTENT_TYPE" => "application/json" }
259 )
260
261 assert last_response.ok?
262 assert_equal(
263 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
264 "<Transfer transferCallerId=\"+15551234567\">" \
265 "<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
266 last_response.body
267 )
268 end
269 em :test_outbound_atlimit_digits
270
271 def test_inbound
272 CustomerFwd::BANDWIDTH_VOICE.expect(
273 :create_call,
274 OpenStruct.new(data: OpenStruct.new(call_id: "ocall")),
275 [
276 "test_bw_account",
277 Matching.new do |arg|
278 assert_equal(
279 "http://example.org/inbound/calls/acall?customer_id=customerid",
280 arg[:body].answer_url
281 )
282 assert_equal [:body], arg.keys
283 end
284 ]
285 )
286
287 post(
288 "/inbound/calls",
289 {
290 from: "+15557654321",
291 to: "+15551234567",
292 callId: "acall"
293 }.to_json,
294 { "CONTENT_TYPE" => "application/json" }
295 )
296
297 assert last_response.ok?
298 assert_equal(
299 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
300 "<Ring answerCall=\"false\" duration=\"300\" />" \
301 "</Response>",
302 last_response.body
303 )
304 assert_mock CustomerFwd::BANDWIDTH_VOICE
305 end
306 em :test_inbound
307
308 def test_inbound_low
309 ExpiringLock::REDIS.expect(
310 :exists,
311 EMPromise.resolve(1),
312 ["jmp_customer_low_balance-customerid_low"]
313 )
314
315 post(
316 "/inbound/calls",
317 {
318 from: "+15557654321",
319 to: "+15551234560",
320 callId: "acall"
321 }.to_json,
322 { "CONTENT_TYPE" => "application/json" }
323 )
324
325 assert last_response.ok?
326 assert_equal(
327 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
328 "<Redirect redirectUrl=\"/inbound/calls/acall/voicemail\" />" \
329 "</Response>",
330 last_response.body
331 )
332 assert_mock CustomerFwd::BANDWIDTH_VOICE
333 assert_mock ExpiringLock::REDIS
334 end
335 em :test_inbound_low
336
337 def test_inbound_leg2
338 post(
339 "/inbound/calls/acall?customer_id=customerid",
340 {
341 from: "+15557654321",
342 to: "sip:boop@example.com",
343 callId: "ocall"
344 }.to_json,
345 { "CONTENT_TYPE" => "application/json" }
346 )
347
348 assert last_response.ok?
349 assert_equal(
350 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
351 "<Tag>connected</Tag><Bridge>acall</Bridge>" \
352 "</Response>",
353 last_response.body
354 )
355 end
356 em :test_inbound_leg2
357
358 def test_inbound_limit_leg2
359 path = "/inbound/calls/acall?customer_id=customerid_limit"
360
361 post(
362 path,
363 {
364 from: "+15557654321",
365 to: "sip:boop@example.com",
366 callId: "ocall"
367 }.to_json,
368 { "CONTENT_TYPE" => "application/json" }
369 )
370
371 assert last_response.ok?
372 assert_equal(
373 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
374 "<Gather gatherUrl=\"#{path}\" maxDigits=\"1\" " \
375 "repeatCount=\"3\"><SpeakSentence>This call will take you over " \
376 "your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
377 "Change your limit in your account settings or press 1 to accept the " \
378 "charges. You can hang up to send the caller to voicemail." \
379 "</SpeakSentence></Gather></Response>",
380 last_response.body
381 )
382 end
383 em :test_inbound_limit_leg2
384
385 def test_inbound_limit_digits_leg2
386 post(
387 "/inbound/calls/acall?customer_id=customerid_limit",
388 {
389 from: "+15557654321",
390 to: "sip:boop@example.com",
391 callId: "ocall",
392 digits: "1"
393 }.to_json,
394 { "CONTENT_TYPE" => "application/json" }
395 )
396
397 assert last_response.ok?
398 assert_equal(
399 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
400 "<Tag>connected</Tag><Bridge>acall</Bridge>" \
401 "</Response>",
402 last_response.body
403 )
404 end
405 em :test_inbound_limit_digits_leg2
406
407 def test_inbound_limit_hangup
408 Web::BANDWIDTH_VOICE.expect(
409 :modify_call,
410 nil,
411 [
412 "test_bw_account",
413 "bcall",
414 Matching.new do |arg|
415 assert_equal [:body], arg.keys
416 assert_equal(
417 "http://example.org/inbound/calls/oocall/voicemail",
418 arg[:body].redirect_url
419 )
420 end
421 ]
422 )
423
424 post(
425 "/inbound/calls/bcall/transfer_complete",
426 {
427 from: "+15557654321",
428 to: "+15551234561",
429 callId: "oocall",
430 cause: "hangup"
431 }.to_json,
432 { "CONTENT_TYPE" => "application/json" }
433 )
434
435 assert last_response.ok?
436 assert_mock Web::BANDWIDTH_VOICE
437 end
438 em :test_inbound_limit_hangup
439
440 def test_voicemail
441 Customer::BLATHER.expect(
442 :<<,
443 nil,
444 [Matching.new do |stanza|
445 assert_equal "+15557654321@component", stanza.from.to_s
446 assert_equal "customer@example.com", stanza.to.to_s
447 assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
448 end]
449 )
450
451 post(
452 "/inbound/calls/CALLID/voicemail/audio",
453 {
454 "startTime" => "2021-01-01T00:00:00Z",
455 "endTime" => "2021-01-01T00:00:06Z",
456 "mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
457 "to" => "+15551234567",
458 "from" => "+15557654321"
459 }.to_json,
460 { "CONTENT_TYPE" => "application/json" }
461 )
462
463 assert last_response.ok?
464 assert_mock Customer::BLATHER
465 end
466 em :test_voicemail
467
468 def test_anonymous_voicemail
469 Customer::BLATHER.expect(
470 :<<,
471 nil,
472 [Matching.new do |stanza|
473 assert_equal(
474 "16;phone-context=anonymous.phone-context.soprani.ca@component",
475 stanza.from.to_s
476 )
477 assert_equal "customer@example.com", stanza.to.to_s
478 assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
479 end]
480 )
481
482 post(
483 "/inbound/calls/CALLID/voicemail/audio",
484 {
485 "startTime" => "2021-01-01T00:00:00Z",
486 "endTime" => "2021-01-01T00:00:06Z",
487 "mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
488 "to" => "+15551234567",
489 "from" => "Anonymous"
490 }.to_json,
491 { "CONTENT_TYPE" => "application/json" }
492 )
493
494 assert last_response.ok?
495 assert_mock Customer::BLATHER
496 end
497 em :test_anonymous_voicemail
498
499 def test_voicemail_short
500 post(
501 "/inbound/calls/CALLID/voicemail/audio",
502 {
503 "startTime" => "2021-01-01T00:00:00Z",
504 "endTime" => "2021-01-01T00:00:05Z"
505 }.to_json,
506 { "CONTENT_TYPE" => "application/json" }
507 )
508
509 assert last_response.ok?
510 assert_mock Customer::BLATHER
511 end
512 em :test_voicemail_short
513end