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