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_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
582end