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