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