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" => IBR.new.tap do |ibr|
 66							ibr.phone = "+15551234567"
 67						end,
 68						"customer_customerid_low@component" => IBR.new.tap do |ibr|
 69							ibr.phone = "+15551234567"
 70						end,
 71						"customer_customerid_topup@component" => IBR.new.tap do |ibr|
 72							ibr.phone = "+15551234567"
 73						end,
 74						"customer_customerid_limit@component" => IBR.new.tap do |ibr|
 75							ibr.phone = "+15551234567"
 76						end
 77					}
 78				)
 79			)
 80		)
 81		Web.opts[:call_attempt_repo] = CallAttemptRepo.new(
 82			db: FakeDB.new(
 83				["test_usd", "+15557654321", :outbound] => [{ "rate" => 0.01 }],
 84				["test_usd", "+15557654321", :inbound] => [{ "rate" => 0.01 }],
 85				["customerid_limit"] => [{ "a" => -1000 }],
 86				["customerid_low"] => [{ "a" => -1000 }],
 87				["customerid_topup"] => [{ "a" => -1000 }]
 88			)
 89		)
 90		Web.opts[:common_logger] = FakeLog.new
 91		Web.instance_variable_set(:@outbound_transfers, { "bcall" => "oocall" })
 92		Web.app
 93	end
 94
 95	def test_outbound_forwards
 96		post(
 97			"/outbound/calls",
 98			{
 99				from: "customerid",
100				to: "+15557654321",
101				callId: "acall"
102			}.to_json,
103			{ "CONTENT_TYPE" => "application/json" }
104		)
105
106		assert last_response.ok?
107		assert_equal(
108			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
109			"<Forward from=\"+15551234567\" to=\"+15557654321\" />" \
110			"</Response>",
111			last_response.body
112		)
113	end
114	em :test_outbound_forwards
115
116	def test_outbound_low_balance
117		ExpiringLock::REDIS.expect(
118			:exists,
119			EMPromise.resolve(1),
120			["jmp_customer_low_balance-customerid_low"]
121		)
122
123		post(
124			"/outbound/calls",
125			{
126				from: "customerid_low",
127				to: "+15557654321",
128				callId: "acall"
129			}.to_json,
130			{ "CONTENT_TYPE" => "application/json" }
131		)
132
133		assert last_response.ok?
134		assert_equal(
135			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
136			"<SpeakSentence>Your balance of $0.01 is not enough to " \
137			"complete this call.</SpeakSentence></Response>",
138			last_response.body
139		)
140		assert_mock ExpiringLock::REDIS
141	end
142	em :test_outbound_low_balance
143
144	def test_outbound_low_balance_top_up
145		LowBalance::AutoTopUp::Transaction.expect(
146			:sale,
147			EMPromise.resolve(
148				OpenStruct.new(insert: EMPromise.resolve(nil), total: 15)
149			),
150			[Customer, { amount: 15 }]
151		)
152
153		ExpiringLock::REDIS.expect(
154			:exists,
155			nil,
156			["jmp_customer_low_balance-customerid_topup"]
157		)
158
159		ExpiringLock::REDIS.expect(
160			:setex,
161			nil,
162			["jmp_customer_low_balance-customerid_topup", Integer, Time]
163		)
164
165		Customer::BLATHER.expect(
166			:<<,
167			nil,
168			[Blather::Stanza]
169		)
170
171		post(
172			"/outbound/calls",
173			{
174				from: "customerid_topup",
175				to: "+15557654321",
176				callId: "acall"
177			}.to_json,
178			{ "CONTENT_TYPE" => "application/json" }
179		)
180
181		assert last_response.ok?
182		assert_equal(
183			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
184			"<Forward from=\"+15551234567\" to=\"+15557654321\" />" \
185			"</Response>",
186			last_response.body
187		)
188		assert_mock ExpiringLock::REDIS
189		assert_mock Customer::BLATHER
190		assert_mock LowBalance::AutoTopUp::Transaction
191	end
192	em :test_outbound_low_balance_top_up
193
194	def test_outbound_unsupported
195		post(
196			"/outbound/calls",
197			{
198				from: "customerid_limit",
199				to: "+95557654321",
200				callId: "acall"
201			}.to_json,
202			{ "CONTENT_TYPE" => "application/json" }
203		)
204
205		assert last_response.ok?
206		assert_equal(
207			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
208			"<SpeakSentence>The number you have dialled is not " \
209			"supported on your account.</SpeakSentence></Response>",
210			last_response.body
211		)
212	end
213	em :test_outbound_unsupported
214
215	def test_outbound_atlimit
216		post(
217			"/outbound/calls",
218			{
219				from: "customerid_limit",
220				to: "+15557654321",
221				callId: "acall"
222			}.to_json,
223			{ "CONTENT_TYPE" => "application/json" }
224		)
225
226		assert last_response.ok?
227		assert_equal(
228			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
229			"<Gather gatherUrl=\"\/outbound/calls\" maxDigits=\"1\" " \
230			"repeatCount=\"3\"><SpeakSentence>This call will take you over " \
231			"your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
232			"Change your limit in your account settings or press 1 to accept the " \
233			"charges. You can hang up to cancel.</SpeakSentence></Gather></Response>",
234			last_response.body
235		)
236	end
237	em :test_outbound_atlimit
238
239	def test_outbound_atlimit_digits
240		post(
241			"/outbound/calls",
242			{
243				from: "customerid_limit",
244				to: "+15557654321",
245				callId: "acall",
246				digits: "1"
247			}.to_json,
248			{ "CONTENT_TYPE" => "application/json" }
249		)
250
251		assert last_response.ok?
252		assert_equal(
253			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
254			"<Forward from=\"+15551234567\" to=\"+15557654321\" />" \
255			"</Response>",
256			last_response.body
257		)
258	end
259	em :test_outbound_atlimit_digits
260
261	def test_inbound
262		CustomerFwd::BANDWIDTH_VOICE.expect(
263			:create_call,
264			OpenStruct.new(data: OpenStruct.new(call_id: "ocall")),
265			[
266				"test_bw_account",
267				Matching.new { |arg| assert_equal [:body], arg.keys }
268			]
269		)
270
271		post(
272			"/inbound/calls",
273			{
274				from: "+15557654321",
275				to: "+15551234567",
276				callId: "acall"
277			}.to_json,
278			{ "CONTENT_TYPE" => "application/json" }
279		)
280
281		assert last_response.ok?
282		assert_equal(
283			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
284			"<Ring answerCall=\"false\" duration=\"300\" />" \
285			"</Response>",
286			last_response.body
287		)
288		assert_mock CustomerFwd::BANDWIDTH_VOICE
289	end
290	em :test_inbound
291
292	def test_inbound_low
293		ExpiringLock::REDIS.expect(
294			:exists,
295			EMPromise.resolve(1),
296			["jmp_customer_low_balance-customerid_low"]
297		)
298
299		post(
300			"/inbound/calls",
301			{
302				from: "+15557654321",
303				to: "+15551234560",
304				callId: "acall"
305			}.to_json,
306			{ "CONTENT_TYPE" => "application/json" }
307		)
308
309		assert last_response.ok?
310		assert_equal(
311			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
312			"<Redirect redirectUrl=\"/inbound/calls/acall/voicemail\" />" \
313			"</Response>",
314			last_response.body
315		)
316		assert_mock CustomerFwd::BANDWIDTH_VOICE
317		assert_mock ExpiringLock::REDIS
318	end
319	em :test_inbound_low
320
321	def test_inbound_leg2
322		post(
323			"/inbound/calls/acall",
324			{
325				from: "+15557654321",
326				to: "+15551234567",
327				callId: "ocall"
328			}.to_json,
329			{ "CONTENT_TYPE" => "application/json" }
330		)
331
332		assert last_response.ok?
333		assert_equal(
334			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
335			"<Tag>connected</Tag><Bridge>acall</Bridge>" \
336			"</Response>",
337			last_response.body
338		)
339	end
340	em :test_inbound_leg2
341
342	def test_inbound_limit_leg2
343		post(
344			"/inbound/calls/acall",
345			{
346				from: "+15557654321",
347				to: "+15551234561",
348				callId: "ocall"
349			}.to_json,
350			{ "CONTENT_TYPE" => "application/json" }
351		)
352
353		assert last_response.ok?
354		assert_equal(
355			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
356			"<Gather gatherUrl=\"\/inbound/calls/acall\" maxDigits=\"1\" " \
357			"repeatCount=\"3\"><SpeakSentence>This call will take you over " \
358			"your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
359			"Change your limit in your account settings or press 1 to accept the " \
360			"charges. You can hang up to send the caller to voicemail." \
361			"</SpeakSentence></Gather></Response>",
362			last_response.body
363		)
364	end
365	em :test_inbound_limit_leg2
366
367	def test_inbound_limit_digits_leg2
368		post(
369			"/inbound/calls/acall",
370			{
371				from: "+15557654321",
372				to: "+15551234561",
373				callId: "ocall",
374				digits: "1"
375			}.to_json,
376			{ "CONTENT_TYPE" => "application/json" }
377		)
378
379		assert last_response.ok?
380		assert_equal(
381			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
382			"<Tag>connected</Tag><Bridge>acall</Bridge>" \
383			"</Response>",
384			last_response.body
385		)
386	end
387	em :test_inbound_limit_digits_leg2
388
389	def test_inbound_limit_hangup
390		Web::BANDWIDTH_VOICE.expect(
391			:modify_call,
392			nil,
393			[
394				"test_bw_account",
395				"bcall",
396				Matching.new do |arg|
397					assert_equal [:body], arg.keys
398					assert_equal(
399						"http://example.org/inbound/calls/oocall/voicemail",
400						arg[:body].redirect_url
401					)
402				end
403			]
404		)
405
406		post(
407			"/inbound/calls/bcall/transfer_complete",
408			{
409				from: "+15557654321",
410				to: "+15551234561",
411				callId: "oocall",
412				cause: "hangup"
413			}.to_json,
414			{ "CONTENT_TYPE" => "application/json" }
415		)
416
417		assert last_response.ok?
418		assert_mock Web::BANDWIDTH_VOICE
419	end
420	em :test_inbound_limit_hangup
421
422	def test_voicemail
423		Customer::BLATHER.expect(
424			:<<,
425			nil,
426			[Matching.new do |stanza|
427				assert_equal "+15557654321@component", stanza.from.to_s
428				assert_equal "customer@example.com", stanza.to.to_s
429				assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
430			end]
431		)
432
433		post(
434			"/inbound/calls/CALLID/voicemail/audio",
435			{
436				"startTime" => "2021-01-01T00:00:00Z",
437				"endTime" => "2021-01-01T00:00:06Z",
438				"mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
439				"to" => "+15551234567",
440				"from" => "+15557654321"
441			}.to_json,
442			{ "CONTENT_TYPE" => "application/json" }
443		)
444
445		assert last_response.ok?
446		assert_mock Customer::BLATHER
447	end
448	em :test_voicemail
449
450	def test_anonymous_voicemail
451		Customer::BLATHER.expect(
452			:<<,
453			nil,
454			[Matching.new do |stanza|
455				assert_equal(
456					"16;phone-context=anonymous.phone-context.soprani.ca@component",
457					stanza.from.to_s
458				)
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" => "Anonymous"
472			}.to_json,
473			{ "CONTENT_TYPE" => "application/json" }
474		)
475
476		assert last_response.ok?
477		assert_mock Customer::BLATHER
478	end
479	em :test_anonymous_voicemail
480
481	def test_voicemail_short
482		post(
483			"/inbound/calls/CALLID/voicemail/audio",
484			{
485				"startTime" => "2021-01-01T00:00:00Z",
486				"endTime" => "2021-01-01T00:00:05Z"
487			}.to_json,
488			{ "CONTENT_TYPE" => "application/json" }
489		)
490
491		assert last_response.ok?
492		assert_mock Customer::BLATHER
493	end
494	em :test_voicemail_short
495end