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