test_web.rb

  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_atlimit_digits
269		post(
270			"/outbound/calls",
271			{
272				from: "customerid_limit",
273				to: "+15557654321",
274				callId: "acall",
275				digits: "1"
276			}.to_json,
277			{ "CONTENT_TYPE" => "application/json" }
278		)
279
280		assert last_response.ok?
281		assert_equal(
282			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
283			"<Transfer transferCallerId=\"+15551234567\">" \
284			"<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
285			last_response.body
286		)
287	end
288	em :test_outbound_atlimit_digits
289
290	def test_inbound
291		CustomerFwd::BANDWIDTH_VOICE.expect(
292			:create_call,
293			OpenStruct.new(data: OpenStruct.new(call_id: "ocall")),
294			[
295				"test_bw_account",
296				Matching.new do |arg|
297					assert_equal(
298						"http://example.org/inbound/calls/acall?customer_id=customerid",
299						arg[:body].answer_url
300					)
301					assert_equal [:body], arg.keys
302				end
303			]
304		)
305
306		post(
307			"/inbound/calls",
308			{
309				from: "+15557654321",
310				to: "+15551234567",
311				callId: "acall"
312			}.to_json,
313			{ "CONTENT_TYPE" => "application/json" }
314		)
315
316		assert last_response.ok?
317		assert_equal(
318			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
319			"<Ring answerCall=\"false\" duration=\"300\" />" \
320			"</Response>",
321			last_response.body
322		)
323		assert_mock CustomerFwd::BANDWIDTH_VOICE
324	end
325	em :test_inbound
326
327	def test_inbound_low
328		ExpiringLock::REDIS.expect(
329			:set,
330			EMPromise.resolve(nil),
331			["jmp_customer_low_balance-customerid_low", Time, "EX", 604800, "NX"]
332		)
333
334		post(
335			"/inbound/calls",
336			{
337				from: "+15557654321",
338				to: "+15551234560",
339				callId: "acall"
340			}.to_json,
341			{ "CONTENT_TYPE" => "application/json" }
342		)
343
344		assert last_response.ok?
345		assert_equal(
346			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
347			"<Redirect redirectUrl=\"/inbound/calls/acall/voicemail\" />" \
348			"</Response>",
349			last_response.body
350		)
351		assert_mock CustomerFwd::BANDWIDTH_VOICE
352		assert_mock ExpiringLock::REDIS
353	end
354	em :test_inbound_low
355
356	def test_inbound_leg2
357		post(
358			"/inbound/calls/acall?customer_id=customerid",
359			{
360				from: "+15557654321",
361				to: "sip:boop@example.com",
362				callId: "ocall"
363			}.to_json,
364			{ "CONTENT_TYPE" => "application/json" }
365		)
366
367		assert last_response.ok?
368		assert_equal(
369			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
370			"<Tag>connected</Tag><Bridge>acall</Bridge>" \
371			"</Response>",
372			last_response.body
373		)
374	end
375	em :test_inbound_leg2
376
377	def test_inbound_limit_leg2
378		path = "/inbound/calls/acall?customer_id=customerid_limit"
379
380		post(
381			path,
382			{
383				from: "+15557654321",
384				to: "sip:boop@example.com",
385				callId: "ocall"
386			}.to_json,
387			{ "CONTENT_TYPE" => "application/json" }
388		)
389
390		assert last_response.ok?
391		assert_equal(
392			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
393			"<Gather gatherUrl=\"#{path}\" maxDigits=\"1\" " \
394			"repeatCount=\"3\"><SpeakSentence>This call will take you over " \
395			"your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
396			"Change your limit in your account settings or press 1 to accept the " \
397			"charges. You can hang up to send the caller to voicemail." \
398			"</SpeakSentence></Gather></Response>",
399			last_response.body
400		)
401	end
402	em :test_inbound_limit_leg2
403
404	def test_inbound_limit_digits_leg2
405		post(
406			"/inbound/calls/acall?customer_id=customerid_limit",
407			{
408				from: "+15557654321",
409				to: "sip:boop@example.com",
410				callId: "ocall",
411				digits: "1"
412			}.to_json,
413			{ "CONTENT_TYPE" => "application/json" }
414		)
415
416		assert last_response.ok?
417		assert_equal(
418			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
419			"<Tag>connected</Tag><Bridge>acall</Bridge>" \
420			"</Response>",
421			last_response.body
422		)
423	end
424	em :test_inbound_limit_digits_leg2
425
426	def test_inbound_limit_hangup
427		Web::BANDWIDTH_VOICE.expect(
428			:modify_call,
429			nil,
430			[
431				"test_bw_account",
432				"bcall",
433				Matching.new do |arg|
434					assert_equal [:body], arg.keys
435					assert_equal(
436						"http://example.org/inbound/calls/oocall/voicemail",
437						arg[:body].redirect_url
438					)
439				end
440			]
441		)
442
443		post(
444			"/inbound/calls/bcall/transfer_complete",
445			{
446				from: "+15557654321",
447				to: "+15551234561",
448				callId: "oocall",
449				cause: "hangup"
450			}.to_json,
451			{ "CONTENT_TYPE" => "application/json" }
452		)
453
454		assert last_response.ok?
455		assert_mock Web::BANDWIDTH_VOICE
456	end
457	em :test_inbound_limit_hangup
458
459	def test_voicemail
460		language_id = stub_request(:post, "https://api.rev.ai/languageid/v1/jobs")
461			.with(body: {
462				metadata: {
463					media_url: "https://jmp.chat/media",
464					from_jid: "+15557654321@component",
465					customer_id: "customerid"
466				}.to_json,
467				source_config: {
468					url: "https://jmp.chat/media"
469				},
470				notification_config: {
471					url: "http://example.org/inbound/calls/CALLID/voicemail/language_id"
472				}
473			}.to_json)
474
475		Customer::BLATHER.expect(
476			:<<,
477			nil,
478			[Matching.new do |stanza|
479				assert_equal "+15557654321@component", stanza.from.to_s
480				assert_equal "customer@example.com", stanza.to.to_s
481				assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
482			end]
483		)
484
485		post(
486			"/inbound/calls/CALLID/voicemail/audio",
487			{
488				"startTime" => "2021-01-01T00:00:00Z",
489				"endTime" => "2021-01-01T00:00:06Z",
490				"mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
491				"to" => "+15551234567",
492				"from" => "+15557654321"
493			}.to_json,
494			{ "CONTENT_TYPE" => "application/json" }
495		)
496
497		assert last_response.ok?
498		assert_mock Customer::BLATHER
499		assert_requested language_id
500	end
501	em :test_voicemail
502
503	def test_anonymous_voicemail
504		language_id = stub_request(:post, "https://api.rev.ai/languageid/v1/jobs")
505			.with(body: {
506				metadata: {
507					media_url: "https://jmp.chat/media",
508					from_jid:
509						"16;phone-context=anonymous.phone-context.soprani.ca@component",
510					customer_id: "customerid"
511				}.to_json,
512				source_config: {
513					url: "https://jmp.chat/media"
514				},
515				notification_config: {
516					url: "http://example.org/inbound/calls/CALLID/voicemail/language_id"
517				}
518			}.to_json)
519
520		Customer::BLATHER.expect(
521			:<<,
522			nil,
523			[Matching.new do |stanza|
524				assert_equal(
525					"16;phone-context=anonymous.phone-context.soprani.ca@component",
526					stanza.from.to_s
527				)
528				assert_equal "customer@example.com", stanza.to.to_s
529				assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
530			end]
531		)
532
533		post(
534			"/inbound/calls/CALLID/voicemail/audio",
535			{
536				"startTime" => "2021-01-01T00:00:00Z",
537				"endTime" => "2021-01-01T00:00:06Z",
538				"mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
539				"to" => "+15551234567",
540				"from" => "Anonymous"
541			}.to_json,
542			{ "CONTENT_TYPE" => "application/json" }
543		)
544
545		assert last_response.ok?
546		assert_mock Customer::BLATHER
547		assert_requested language_id
548	end
549	em :test_anonymous_voicemail
550
551	def test_voicemail_short
552		post(
553			"/inbound/calls/CALLID/voicemail/audio",
554			{
555				"startTime" => "2021-01-01T00:00:00Z",
556				"endTime" => "2021-01-01T00:00:05Z"
557			}.to_json,
558			{ "CONTENT_TYPE" => "application/json" }
559		)
560
561		assert last_response.ok?
562		assert_mock Customer::BLATHER
563	end
564	em :test_voicemail_short
565end