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::CreditCardSale = 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				"jmp_customer_jid-customerid2" => "customer2@example.com",
 32				"catapult_jid-+15551230000" => "customer_customerid2@component"
 33			),
 34			db: FakeDB.new(
 35				["customerid"] => [{
 36					"balance" => BigDecimal(10),
 37					"plan_name" => "test_usd",
 38					"expires_at" => Time.now + 100
 39				}],
 40				["customerid2"] => [{
 41					"balance" => BigDecimal(10),
 42					"plan_name" => "test_usd",
 43					"expires_at" => Time.now + 100
 44				}],
 45				["customerid_low"] => [{
 46					"balance" => BigDecimal("0.01"),
 47					"plan_name" => "test_usd",
 48					"expires_at" => Time.now + 100
 49				}],
 50				["customerid_topup"] => [{
 51					"balance" => BigDecimal("0.01"),
 52					"plan_name" => "test_usd",
 53					"expires_at" => Time.now + 100
 54				}],
 55				["customerid_limit"] => [{
 56					"balance" => BigDecimal(10),
 57					"plan_name" => "test_usd",
 58					"expires_at" => Time.now + 100
 59				}]
 60			),
 61			sgx_repo: Bwmsgsv2Repo.new(
 62				redis: FakeRedis.new(
 63					"catapult_fwd-+15551234567" => "xmpp:customer@example.com",
 64					"catapult_fwd_timeout-customer_customerid@component" => "30",
 65					"catapult_fwd-+15551234560" => "xmpp:customer@example.com",
 66					"catapult_fwd_timeout-customer_customerid_low@component" => "30",
 67					"catapult_fwd-+15551234561" => "xmpp:customer@example.com",
 68					"catapult_fwd_timeout-customer_customerid_limit@component" => "30",
 69					"catapult_fwd-+15551230000" => "xmpp:customer2@example.com",
 70					"catapult_fwd_timeout-customer_customerid2@component" => "30"
 71				),
 72				ibr_repo: FakeIBRRepo.new(
 73					"sgx" => {
 74						"customer_customerid@component" =>
 75							Blather::Stanza::Iq::IBR.new.tap do |ibr|
 76								ibr.phone = "+15551234567"
 77							end,
 78						"customer_customerid_low@component" =>
 79							Blather::Stanza::Iq::IBR.new.tap do |ibr|
 80								ibr.phone = "+15551234567"
 81							end,
 82						"customer_customerid_topup@component" =>
 83							Blather::Stanza::Iq::IBR.new.tap do |ibr|
 84								ibr.phone = "+15551234567"
 85							end,
 86						"customer_customerid_limit@component" =>
 87							Blather::Stanza::Iq::IBR.new.tap do |ibr|
 88								ibr.phone = "+15551234567"
 89							end
 90					}
 91				)
 92			)
 93		)
 94		Web.opts[:call_attempt_repo] = CallAttemptRepo.new(
 95			redis: FakeRedis.new,
 96			db: FakeDB.new(
 97				["test_usd", "+15557654321", :outbound] => [{ "rate" => 0.01 }],
 98				["test_usd", "+15557654321", :inbound] => [{ "rate" => 0.01 }],
 99				["customerid_limit"] => FakeDB::MultiResult.new(
100					[{ "a" => 1000 }],
101					[{ "settled_amount" => 15 }]
102				),
103				["customerid_low"] => FakeDB::MultiResult.new(
104					[{ "a" => 1000 }],
105					[{ "settled_amount" => 15 }]
106				),
107				["customerid_topup"] => FakeDB::MultiResult.new(
108					[{ "a" => 1000 }],
109					[{ "settled_amount" => 15 }]
110				)
111			)
112		)
113		Web.opts[:common_logger] = FakeLog.new
114		Web.instance_variable_set(:@outbound_transfers, { "bcall" => "oocall" })
115		Web.app
116	end
117
118	def test_outbound_forwards
119		post(
120			"/outbound/calls",
121			{
122				from: "ccustomerid",
123				to: "+15557654321",
124				callId: "acall"
125			}.to_json,
126			{ "CONTENT_TYPE" => "application/json" }
127		)
128
129		assert last_response.ok?
130		assert_equal(
131			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
132			"<Transfer transferCallerId=\"+15551234567\">" \
133			"<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
134			last_response.body
135		)
136	end
137	em :test_outbound_forwards
138
139	def test_outbound_low_balance
140		ExpiringLock::REDIS.expect(
141			:set,
142			EMPromise.resolve(nil),
143			["jmp_customer_low_balance-customerid_low", Time, "EX", 604800, "NX"]
144		)
145
146		post(
147			"/outbound/calls",
148			{
149				from: "ccustomerid_low",
150				to: "+15557654321",
151				callId: "acall"
152			}.to_json,
153			{ "CONTENT_TYPE" => "application/json" }
154		)
155
156		assert last_response.ok?
157		assert_equal(
158			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
159			"<SpeakSentence>Your balance of $0.01 is not enough to " \
160			"complete this call.</SpeakSentence></Response>",
161			last_response.body
162		)
163		assert_mock ExpiringLock::REDIS
164	end
165	em :test_outbound_low_balance
166
167	def test_outbound_low_balance_top_up
168		LowBalance::AutoTopUp::CreditCardSale.expect(
169			:create,
170			EMPromise.resolve(
171				OpenStruct.new(total: 15)
172			),
173			[Customer], amount: 15
174		)
175
176		ExpiringLock::REDIS.expect(
177			:set,
178			EMPromise.resolve("OK"),
179			["jmp_customer_low_balance-customerid_topup", Time, "EX", 604800, "NX"]
180		)
181
182		CustomerFinancials::REDIS.expect(
183			:smembers,
184			EMPromise.resolve([]),
185			["block_credit_cards"]
186		)
187		LowBalance::AutoTopUp::REDIS.expect(
188			:exists,
189			0,
190			["jmp_auto_top_up_block-abcd"]
191		)
192		braintree_customer = Minitest::Mock.new
193		CustomerFinancials::BRAINTREE.expect(:customer, braintree_customer)
194		payment_methods = OpenStruct.new(payment_methods: [
195			OpenStruct.new(default?: true, unique_number_identifier: "abcd")
196		])
197		braintree_customer.expect(
198			:find,
199			EMPromise.resolve(payment_methods),
200			["customerid_topup"]
201		)
202
203		Customer::BLATHER.expect(
204			:<<,
205			nil,
206			[Blather::Stanza]
207		)
208
209		post(
210			"/outbound/calls",
211			{
212				from: "ccustomerid_topup",
213				to: "+15557654321",
214				callId: "acall"
215			}.to_json,
216			{ "CONTENT_TYPE" => "application/json" }
217		)
218
219		assert last_response.ok?
220		assert_equal(
221			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
222			"<Transfer transferCallerId=\"+15551234567\">" \
223			"<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
224			last_response.body
225		)
226		assert_mock ExpiringLock::REDIS
227		assert_mock Customer::BLATHER
228		assert_mock LowBalance::AutoTopUp::CreditCardSale
229	end
230	em :test_outbound_low_balance_top_up
231
232	def test_outbound_unsupported
233		post(
234			"/outbound/calls",
235			{
236				from: "ccustomerid_limit",
237				to: "+95557654321",
238				callId: "acall"
239			}.to_json,
240			{ "CONTENT_TYPE" => "application/json" }
241		)
242
243		assert last_response.ok?
244		assert_equal(
245			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
246			"<SpeakSentence>The number you have dialled is not " \
247			"supported on your account.</SpeakSentence></Response>",
248			last_response.body
249		)
250	end
251	em :test_outbound_unsupported
252
253	def test_outbound_atlimit
254		post(
255			"/outbound/calls",
256			{
257				from: "ccustomerid_limit",
258				to: "+15557654321",
259				callId: "acall"
260			}.to_json,
261			{ "CONTENT_TYPE" => "application/json" }
262		)
263
264		assert last_response.ok?
265		assert_equal(
266			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
267			"<Gather gatherUrl=\"\/outbound/calls\" maxDigits=\"1\" " \
268			"repeatCount=\"3\"><SpeakSentence>This call will take you over " \
269			"your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
270			"Change your limit in your account settings or press 1 to accept the " \
271			"charges. You can hang up to cancel.</SpeakSentence></Gather></Response>",
272			last_response.body
273		)
274	end
275	em :test_outbound_atlimit
276
277	def test_outbound_no_customer
278		post(
279			"/outbound/calls",
280			{
281				from: "no_such_customer",
282				to: "+15557654321",
283				callId: "acall"
284			}.to_json,
285			{ "CONTENT_TYPE" => "application/json" }
286		)
287
288		assert last_response.ok?
289		assert_equal(
290			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
291			"<SpeakSentence>Your credentials are invalid, please contact support." \
292			"</SpeakSentence></Response>",
293			last_response.body
294		)
295	end
296	em :test_outbound_no_customer
297
298	def test_outbound_atlimit_digits
299		post(
300			"/outbound/calls",
301			{
302				from: "ccustomerid_limit",
303				to: "+15557654321",
304				callId: "acall",
305				digits: "1"
306			}.to_json,
307			{ "CONTENT_TYPE" => "application/json" }
308		)
309
310		assert last_response.ok?
311		assert_equal(
312			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
313			"<Transfer transferCallerId=\"+15551234567\">" \
314			"<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
315			last_response.body
316		)
317	end
318	em :test_outbound_atlimit_digits
319
320	def test_inbound
321		CustomerFwd::BANDWIDTH_VOICE.expect(
322			:create_call,
323			OpenStruct.new(data: OpenStruct.new(call_id: "ocall")),
324			["test_bw_account"],
325			body: Matching.new do |arg|
326				assert_equal(
327					"http://example.org/inbound/calls/acall?customer_id=customerid",
328					arg.answer_url
329				)
330			end
331		)
332
333		post(
334			"/inbound/calls",
335			{
336				from: "+15557654321",
337				to: "+15551234567",
338				callId: "acall"
339			}.to_json,
340			{ "CONTENT_TYPE" => "application/json" }
341		)
342
343		assert last_response.ok?
344		assert_equal(
345			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
346			"<Ring answerCall=\"false\" duration=\"300\" />" \
347			"</Response>",
348			last_response.body
349		)
350		assert_mock CustomerFwd::BANDWIDTH_VOICE
351	end
352	em :test_inbound
353
354	def test_inbound_no_bwmsgsv2
355		CustomerFwd::BANDWIDTH_VOICE.expect(
356			:create_call,
357			OpenStruct.new(data: OpenStruct.new(call_id: "ocall")),
358			["test_bw_account"],
359			body: Matching.new do |arg|
360				assert_equal(
361					"http://example.org/inbound/calls/acall?customer_id=customerid2",
362					arg.answer_url
363				)
364			end
365		)
366
367		post(
368			"/inbound/calls",
369			{
370				from: "+15557654321",
371				to: "+15551230000",
372				callId: "acall"
373			}.to_json,
374			{ "CONTENT_TYPE" => "application/json" }
375		)
376
377		assert last_response.ok?
378		assert_equal(
379			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
380			"<Ring answerCall=\"false\" duration=\"300\" />" \
381			"</Response>",
382			last_response.body
383		)
384		assert_mock CustomerFwd::BANDWIDTH_VOICE
385	end
386	em :test_inbound_no_bwmsgsv2
387
388	def test_inbound_low
389		ExpiringLock::REDIS.expect(
390			:set,
391			EMPromise.resolve(nil),
392			["jmp_customer_low_balance-customerid_low", Time, "EX", 604800, "NX"]
393		)
394
395		post(
396			"/inbound/calls",
397			{
398				from: "+15557654321",
399				to: "+15551234560",
400				callId: "acall"
401			}.to_json,
402			{ "CONTENT_TYPE" => "application/json" }
403		)
404
405		assert last_response.ok?
406		assert_equal(
407			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
408			"<Redirect redirectUrl=\"/inbound/calls/acall/voicemail\" />" \
409			"</Response>",
410			last_response.body
411		)
412		assert_mock CustomerFwd::BANDWIDTH_VOICE
413		assert_mock ExpiringLock::REDIS
414	end
415	em :test_inbound_low
416
417	def test_inbound_leg2
418		post(
419			"/inbound/calls/acall?customer_id=customerid",
420			{
421				from: "+15557654321",
422				to: "sip:boop@example.com",
423				callId: "ocall"
424			}.to_json,
425			{ "CONTENT_TYPE" => "application/json" }
426		)
427
428		assert last_response.ok?
429		assert_equal(
430			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
431			"<Tag>connected</Tag><Bridge>acall</Bridge>" \
432			"</Response>",
433			last_response.body
434		)
435	end
436	em :test_inbound_leg2
437
438	def test_inbound_limit_leg2
439		path = "/inbound/calls/acall?customer_id=customerid_limit"
440
441		post(
442			path,
443			{
444				from: "+15557654321",
445				to: "sip:boop@example.com",
446				callId: "ocall"
447			}.to_json,
448			{ "CONTENT_TYPE" => "application/json" }
449		)
450
451		assert last_response.ok?
452		assert_equal(
453			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
454			"<Gather gatherUrl=\"#{path}\" maxDigits=\"1\" " \
455			"repeatCount=\"3\"><SpeakSentence>This call will take you over " \
456			"your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
457			"Change your limit in your account settings or press 1 to accept the " \
458			"charges. You can hang up to send the caller to voicemail." \
459			"</SpeakSentence></Gather></Response>",
460			last_response.body
461		)
462	end
463	em :test_inbound_limit_leg2
464
465	def test_inbound_limit_digits_leg2
466		post(
467			"/inbound/calls/acall?customer_id=customerid_limit",
468			{
469				from: "+15557654321",
470				to: "sip:boop@example.com",
471				callId: "ocall",
472				digits: "1"
473			}.to_json,
474			{ "CONTENT_TYPE" => "application/json" }
475		)
476
477		assert last_response.ok?
478		assert_equal(
479			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
480			"<Tag>connected</Tag><Bridge>acall</Bridge>" \
481			"</Response>",
482			last_response.body
483		)
484	end
485	em :test_inbound_limit_digits_leg2
486
487	def test_inbound_limit_hangup
488		Web::BANDWIDTH_VOICE.expect(
489			:modify_call,
490			nil,
491			[
492				"test_bw_account",
493				"bcall"
494			],
495			body: Matching.new do |arg|
496				assert_equal(
497					"http://example.org/inbound/calls/oocall/voicemail",
498					arg.redirect_url
499				)
500			end
501		)
502
503		post(
504			"/inbound/calls/bcall/transfer_complete",
505			{
506				from: "+15557654321",
507				to: "+15551234561",
508				callId: "oocall",
509				cause: "hangup"
510			}.to_json,
511			{ "CONTENT_TYPE" => "application/json" }
512		)
513
514		assert last_response.ok?
515		assert_mock Web::BANDWIDTH_VOICE
516	end
517	em :test_inbound_limit_hangup
518
519	def test_voicemail
520		language_id = stub_request(:post, "https://api.rev.ai/languageid/v1/jobs")
521			.with(body: {
522				metadata: {
523					media_url: "https://jmp.chat/media",
524					from_jid: "+15557654321@component",
525					customer_id: "customerid"
526				}.to_json,
527				source_config: {
528					url: "https://jmp.chat/media"
529				},
530				notification_config: {
531					url: "http://example.org/inbound/calls/CALLID/voicemail/language_id"
532				}
533			}.to_json)
534
535		Customer::BLATHER.expect(
536			:<<,
537			nil,
538			[Matching.new do |stanza|
539				assert_equal "+15557654321@component", stanza.from.to_s
540				assert_equal "customer@example.com", stanza.to.to_s
541				assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
542			end]
543		)
544
545		post(
546			"/inbound/calls/CALLID/voicemail/audio",
547			{
548				"startTime" => "2021-01-01T00:00:00Z",
549				"endTime" => "2021-01-01T00:00:06Z",
550				"mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
551				"to" => "+15551234567",
552				"from" => "+15557654321"
553			}.to_json,
554			{ "CONTENT_TYPE" => "application/json" }
555		)
556
557		assert last_response.ok?
558		assert_mock Customer::BLATHER
559		assert_requested language_id
560	end
561	em :test_voicemail
562
563	def test_anonymous_voicemail
564		language_id = stub_request(:post, "https://api.rev.ai/languageid/v1/jobs")
565			.with(body: {
566				metadata: {
567					media_url: "https://jmp.chat/media",
568					from_jid:
569						"16;phone-context=anonymous.phone-context.soprani.ca@component",
570					customer_id: "customerid"
571				}.to_json,
572				source_config: {
573					url: "https://jmp.chat/media"
574				},
575				notification_config: {
576					url: "http://example.org/inbound/calls/CALLID/voicemail/language_id"
577				}
578			}.to_json)
579
580		Customer::BLATHER.expect(
581			:<<,
582			nil,
583			[Matching.new do |stanza|
584				assert_equal(
585					"16;phone-context=anonymous.phone-context.soprani.ca@component",
586					stanza.from.to_s
587				)
588				assert_equal "customer@example.com", stanza.to.to_s
589				assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
590			end]
591		)
592
593		post(
594			"/inbound/calls/CALLID/voicemail/audio",
595			{
596				"startTime" => "2021-01-01T00:00:00Z",
597				"endTime" => "2021-01-01T00:00:06Z",
598				"mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
599				"to" => "+15551234567",
600				"from" => "Anonymous"
601			}.to_json,
602			{ "CONTENT_TYPE" => "application/json" }
603		)
604
605		assert last_response.ok?
606		assert_mock Customer::BLATHER
607		assert_requested language_id
608	end
609	em :test_anonymous_voicemail
610
611	def test_voicemail_short
612		post(
613			"/inbound/calls/CALLID/voicemail/audio",
614			{
615				"startTime" => "2021-01-01T00:00:00Z",
616				"endTime" => "2021-01-01T00:00:05Z"
617			}.to_json,
618			{ "CONTENT_TYPE" => "application/json" }
619		)
620
621		assert last_response.ok?
622		assert_mock Customer::BLATHER
623	end
624	em :test_voicemail_short
625
626	def test_voicemail_no_customer
627		post(
628			"/inbound/calls/CALLID/voicemail",
629			{
630				"startTime" => "2021-01-01T00:00:00Z",
631				"endTime" => "2021-01-01T00:00:06Z",
632				"mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
633				"to" => "+15551200000",
634				"from" => "+15557654321"
635			}.to_json,
636			{ "CONTENT_TYPE" => "application/json" }
637		)
638
639		assert last_response.ok?
640		assert_equal(
641			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
642			"<SpeakSentence>The number you have dialled is not in service." \
643			"</SpeakSentence></Response>",
644			last_response.body
645		)
646	end
647	em :test_voicemail_no_customer
648end