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