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 Customer::BLATHER.expect(
174 :<<,
175 nil,
176 [Blather::Stanza]
177 )
178
179 post(
180 "/outbound/calls",
181 {
182 from: "customerid_topup",
183 to: "+15557654321",
184 callId: "acall"
185 }.to_json,
186 { "CONTENT_TYPE" => "application/json" }
187 )
188
189 assert last_response.ok?
190 assert_equal(
191 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
192 "<Transfer transferCallerId=\"+15551234567\">" \
193 "<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
194 last_response.body
195 )
196 assert_mock ExpiringLock::REDIS
197 assert_mock Customer::BLATHER
198 assert_mock LowBalance::AutoTopUp::Transaction
199 end
200 em :test_outbound_low_balance_top_up
201
202 def test_outbound_unsupported
203 post(
204 "/outbound/calls",
205 {
206 from: "customerid_limit",
207 to: "+95557654321",
208 callId: "acall"
209 }.to_json,
210 { "CONTENT_TYPE" => "application/json" }
211 )
212
213 assert last_response.ok?
214 assert_equal(
215 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
216 "<SpeakSentence>The number you have dialled is not " \
217 "supported on your account.</SpeakSentence></Response>",
218 last_response.body
219 )
220 end
221 em :test_outbound_unsupported
222
223 def test_outbound_atlimit
224 post(
225 "/outbound/calls",
226 {
227 from: "customerid_limit",
228 to: "+15557654321",
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 "<Gather gatherUrl=\"\/outbound/calls\" maxDigits=\"1\" " \
238 "repeatCount=\"3\"><SpeakSentence>This call will take you over " \
239 "your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
240 "Change your limit in your account settings or press 1 to accept the " \
241 "charges. You can hang up to cancel.</SpeakSentence></Gather></Response>",
242 last_response.body
243 )
244 end
245 em :test_outbound_atlimit
246
247 def test_outbound_atlimit_digits
248 post(
249 "/outbound/calls",
250 {
251 from: "customerid_limit",
252 to: "+15557654321",
253 callId: "acall",
254 digits: "1"
255 }.to_json,
256 { "CONTENT_TYPE" => "application/json" }
257 )
258
259 assert last_response.ok?
260 assert_equal(
261 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
262 "<Transfer transferCallerId=\"+15551234567\">" \
263 "<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
264 last_response.body
265 )
266 end
267 em :test_outbound_atlimit_digits
268
269 def test_inbound
270 CustomerFwd::BANDWIDTH_VOICE.expect(
271 :create_call,
272 OpenStruct.new(data: OpenStruct.new(call_id: "ocall")),
273 [
274 "test_bw_account",
275 Matching.new do |arg|
276 assert_equal(
277 "http://example.org/inbound/calls/acall?customer_id=customerid",
278 arg[:body].answer_url
279 )
280 assert_equal [:body], arg.keys
281 end
282 ]
283 )
284
285 post(
286 "/inbound/calls",
287 {
288 from: "+15557654321",
289 to: "+15551234567",
290 callId: "acall"
291 }.to_json,
292 { "CONTENT_TYPE" => "application/json" }
293 )
294
295 assert last_response.ok?
296 assert_equal(
297 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
298 "<Ring answerCall=\"false\" duration=\"300\" />" \
299 "</Response>",
300 last_response.body
301 )
302 assert_mock CustomerFwd::BANDWIDTH_VOICE
303 end
304 em :test_inbound
305
306 def test_inbound_low
307 ExpiringLock::REDIS.expect(
308 :set,
309 EMPromise.resolve(nil),
310 ["jmp_customer_low_balance-customerid_low", Time, "EX", 604800, "NX"]
311 )
312
313 post(
314 "/inbound/calls",
315 {
316 from: "+15557654321",
317 to: "+15551234560",
318 callId: "acall"
319 }.to_json,
320 { "CONTENT_TYPE" => "application/json" }
321 )
322
323 assert last_response.ok?
324 assert_equal(
325 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
326 "<Redirect redirectUrl=\"/inbound/calls/acall/voicemail\" />" \
327 "</Response>",
328 last_response.body
329 )
330 assert_mock CustomerFwd::BANDWIDTH_VOICE
331 assert_mock ExpiringLock::REDIS
332 end
333 em :test_inbound_low
334
335 def test_inbound_leg2
336 post(
337 "/inbound/calls/acall?customer_id=customerid",
338 {
339 from: "+15557654321",
340 to: "sip:boop@example.com",
341 callId: "ocall"
342 }.to_json,
343 { "CONTENT_TYPE" => "application/json" }
344 )
345
346 assert last_response.ok?
347 assert_equal(
348 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
349 "<Tag>connected</Tag><Bridge>acall</Bridge>" \
350 "</Response>",
351 last_response.body
352 )
353 end
354 em :test_inbound_leg2
355
356 def test_inbound_limit_leg2
357 path = "/inbound/calls/acall?customer_id=customerid_limit"
358
359 post(
360 path,
361 {
362 from: "+15557654321",
363 to: "sip:boop@example.com",
364 callId: "ocall"
365 }.to_json,
366 { "CONTENT_TYPE" => "application/json" }
367 )
368
369 assert last_response.ok?
370 assert_equal(
371 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
372 "<Gather gatherUrl=\"#{path}\" maxDigits=\"1\" " \
373 "repeatCount=\"3\"><SpeakSentence>This call will take you over " \
374 "your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
375 "Change your limit in your account settings or press 1 to accept the " \
376 "charges. You can hang up to send the caller to voicemail." \
377 "</SpeakSentence></Gather></Response>",
378 last_response.body
379 )
380 end
381 em :test_inbound_limit_leg2
382
383 def test_inbound_limit_digits_leg2
384 post(
385 "/inbound/calls/acall?customer_id=customerid_limit",
386 {
387 from: "+15557654321",
388 to: "sip:boop@example.com",
389 callId: "ocall",
390 digits: "1"
391 }.to_json,
392 { "CONTENT_TYPE" => "application/json" }
393 )
394
395 assert last_response.ok?
396 assert_equal(
397 "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
398 "<Tag>connected</Tag><Bridge>acall</Bridge>" \
399 "</Response>",
400 last_response.body
401 )
402 end
403 em :test_inbound_limit_digits_leg2
404
405 def test_inbound_limit_hangup
406 Web::BANDWIDTH_VOICE.expect(
407 :modify_call,
408 nil,
409 [
410 "test_bw_account",
411 "bcall",
412 Matching.new do |arg|
413 assert_equal [:body], arg.keys
414 assert_equal(
415 "http://example.org/inbound/calls/oocall/voicemail",
416 arg[:body].redirect_url
417 )
418 end
419 ]
420 )
421
422 post(
423 "/inbound/calls/bcall/transfer_complete",
424 {
425 from: "+15557654321",
426 to: "+15551234561",
427 callId: "oocall",
428 cause: "hangup"
429 }.to_json,
430 { "CONTENT_TYPE" => "application/json" }
431 )
432
433 assert last_response.ok?
434 assert_mock Web::BANDWIDTH_VOICE
435 end
436 em :test_inbound_limit_hangup
437
438 def test_voicemail
439 Customer::BLATHER.expect(
440 :<<,
441 nil,
442 [Matching.new do |stanza|
443 assert_equal "+15557654321@component", stanza.from.to_s
444 assert_equal "customer@example.com", stanza.to.to_s
445 assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
446 end]
447 )
448
449 post(
450 "/inbound/calls/CALLID/voicemail/audio",
451 {
452 "startTime" => "2021-01-01T00:00:00Z",
453 "endTime" => "2021-01-01T00:00:06Z",
454 "mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
455 "to" => "+15551234567",
456 "from" => "+15557654321"
457 }.to_json,
458 { "CONTENT_TYPE" => "application/json" }
459 )
460
461 assert last_response.ok?
462 assert_mock Customer::BLATHER
463 end
464 em :test_voicemail
465
466 def test_anonymous_voicemail
467 Customer::BLATHER.expect(
468 :<<,
469 nil,
470 [Matching.new do |stanza|
471 assert_equal(
472 "16;phone-context=anonymous.phone-context.soprani.ca@component",
473 stanza.from.to_s
474 )
475 assert_equal "customer@example.com", stanza.to.to_s
476 assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
477 end]
478 )
479
480 post(
481 "/inbound/calls/CALLID/voicemail/audio",
482 {
483 "startTime" => "2021-01-01T00:00:00Z",
484 "endTime" => "2021-01-01T00:00:06Z",
485 "mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
486 "to" => "+15551234567",
487 "from" => "Anonymous"
488 }.to_json,
489 { "CONTENT_TYPE" => "application/json" }
490 )
491
492 assert last_response.ok?
493 assert_mock Customer::BLATHER
494 end
495 em :test_anonymous_voicemail
496
497 def test_voicemail_short
498 post(
499 "/inbound/calls/CALLID/voicemail/audio",
500 {
501 "startTime" => "2021-01-01T00:00:00Z",
502 "endTime" => "2021-01-01T00:00:05Z"
503 }.to_json,
504 { "CONTENT_TYPE" => "application/json" }
505 )
506
507 assert last_response.ok?
508 assert_mock Customer::BLATHER
509 end
510 em :test_voicemail_short
511end