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::CreditCardSale = Minitest::Mock.new
 14
 15ReachableRedis = Minitest::Mock.new
 16
 17class WebTest < Minitest::Test
 18	include Rack::Test::Methods
 19
 20	def app
 21		Web.opts[:customer_repo] = CustomerRepo.new(
 22			redis: FakeRedis.new(
 23				"jmp_customer_jid-customerid" => "customer@example.com",
 24				"catapult_jid-+15551234567" => "customer_customerid@component",
 25				"jmp_customer_jid-customerid_low" => "customer@example.com",
 26				"catapult_jid-+15551234560" => "customer_customerid_low@component",
 27				"jmp_customer_jid-customerid_topup" => "customer@example.com",
 28				"jmp_customer_auto_top_up_amount-customerid_topup" => "15",
 29				"jmp_customer_monthly_overage_limit-customerid_topup" => "99999",
 30				"catapult_jid-+15551234562" => "customer_customerid_topup@component",
 31				"jmp_customer_jid-customerid_limit" => "customer@example.com",
 32				"catapult_jid-+15551234561" => "customer_customerid_limit@component",
 33				"jmp_customer_jid-customerid_reach" => "customerid_reach@example.com",
 34				"catapult_jid-+15551234563" => "customer_customerid_reach@component",
 35				"jmp_customer_jid-customerid2" => "customer2@example.com",
 36				"catapult_jid-+15551230000" => "customer_customerid2@component"
 37			),
 38			db: FakeDB.new(
 39				["customerid"] => [{
 40					"balance" => BigDecimal(10),
 41					"plan_name" => "test_usd",
 42					"expires_at" => Time.now + 100
 43				}],
 44				["customerid2"] => [{
 45					"balance" => BigDecimal(10),
 46					"plan_name" => "test_usd",
 47					"expires_at" => Time.now + 100
 48				}],
 49				["customerid_low"] => [{
 50					"balance" => BigDecimal("0.01"),
 51					"plan_name" => "test_usd",
 52					"expires_at" => Time.now + 100
 53				}],
 54				["customerid_topup"] => [{
 55					"balance" => BigDecimal("0.01"),
 56					"plan_name" => "test_usd",
 57					"expires_at" => Time.now + 100
 58				}],
 59				["customerid_reach"] => [{
 60					"balance" => BigDecimal(10),
 61					"plan_name" => "test_usd",
 62					"expires_at" => Time.now + 100
 63				}],
 64				["customerid_limit"] => [{
 65					"balance" => BigDecimal(10),
 66					"plan_name" => "test_usd",
 67					"expires_at" => Time.now + 100
 68				}]
 69			),
 70			sgx_repo: Bwmsgsv2Repo.new(
 71				redis: FakeRedis.new(
 72					"catapult_fwd-+15551234567" => "xmpp:customer@example.com",
 73					"catapult_fwd_timeout-customer_customerid@component" => "30",
 74					"catapult_fwd-+15551234560" => "xmpp:customer@example.com",
 75					"catapult_fwd_timeout-customer_customerid_low@component" => "30",
 76					"catapult_fwd-+15551234561" => "xmpp:customer@example.com",
 77					"catapult_fwd_timeout-customer_customerid_limit@component" => "30",
 78					"catapult_fwd-+15551234563" => "xmpp:customer@example.com",
 79					"catapult_fwd_timeout-customer_customerid_reach@component" => "30",
 80					"catapult_fwd-+15551230000" => "xmpp:customer2@example.com",
 81					"catapult_fwd_timeout-customer_customerid2@component" => "30"
 82				),
 83				ibr_repo: FakeIBRRepo.new(
 84					"sgx" => {
 85						"customer_customerid@component" =>
 86							Blather::Stanza::Iq::IBR.new.tap do |ibr|
 87								ibr.phone = "+15551234567"
 88							end,
 89						"customer_customerid_low@component" =>
 90							Blather::Stanza::Iq::IBR.new.tap do |ibr|
 91								ibr.phone = "+15551234567"
 92							end,
 93						"customer_customerid_topup@component" =>
 94							Blather::Stanza::Iq::IBR.new.tap do |ibr|
 95								ibr.phone = "+15551234567"
 96							end,
 97						"customer_customerid_reach@component" =>
 98							Blather::Stanza::Iq::IBR.new.tap do |ibr|
 99								ibr.phone = "+15551234563"
100							end,
101						"customer_customerid_limit@component" =>
102							Blather::Stanza::Iq::IBR.new.tap do |ibr|
103								ibr.phone = "+15551234567"
104							end
105					}
106				)
107			)
108		)
109		Web.opts[:call_attempt_repo] = CallAttemptRepo.new(
110			redis: FakeRedis.new,
111			db: FakeDB.new(
112				["test_usd", "+15557654321", :outbound] => [{ "rate" => 0.01 }],
113				["test_usd", "+1911", :outbound] => [{ "rate" => 0.01 }],
114				["test_usd", "+19116", :outbound] => [{ "rate" => 0.01 }],
115				["test_usd", "+15557654321", :inbound] => [{ "rate" => 0.01 }],
116				["test_usd", "+14445556666", :inbound] => [{ "rate" => 0.01 }],
117				["test_usd", "+18001234567", :outbound] => [{ "rate" => 0.00 }],
118				["customerid_limit"] => FakeDB::MultiResult.new(
119					[{ "a" => 1000 }],
120					[{ "settled_amount" => 15 }]
121				),
122				["customerid_low"] => FakeDB::MultiResult.new(
123					[{ "a" => 1000 }],
124					[{ "settled_amount" => 15 }]
125				),
126				["customerid_topup"] => FakeDB::MultiResult.new(
127					[{ "a" => 1000 }],
128					[{ "settled_amount" => 15 }]
129				)
130			)
131		)
132		Web.opts[:cdr_repo] = CDRRepo.new(db: FakeDB.new)
133		Web.opts[:common_logger] = FakeLog.new
134		Web.opts[:reachability_repo] = ReachabilityRepo::Voice.new(
135			redis: ReachableRedis,
136			senders: ["+14445556666"]
137		)
138		Web.instance_variable_set(:@outbound_transfers, { "bcall" => "oocall" })
139		Web.app
140	end
141
142	def test_outbound_forwards
143		post(
144			"/outbound/calls",
145			{
146				from: "ccustomerid",
147				to: "+15557654321",
148				callId: "acall"
149			}.to_json,
150			{ "CONTENT_TYPE" => "application/json" }
151		)
152
153		assert last_response.ok?
154		assert_equal(
155			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
156			"<Transfer transferCallerId=\"+15551234567\">" \
157			"<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
158			last_response.body
159		)
160	end
161	em :test_outbound_forwards
162
163	def test_outbound_low_balance
164		ExpiringLock::REDIS.expect(
165			:set,
166			EMPromise.resolve(nil),
167			["jmp_customer_low_balance-customerid_low", Time, "EX", 604800, "NX"]
168		)
169
170		post(
171			"/outbound/calls",
172			{
173				from: "ccustomerid_low",
174				to: "+15557654321",
175				callId: "acall"
176			}.to_json,
177			{ "CONTENT_TYPE" => "application/json" }
178		)
179
180		assert last_response.ok?
181		assert_equal(
182			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
183			"<SpeakSentence>Your balance of $0.01 is not enough to " \
184			"complete this call.</SpeakSentence></Response>",
185			last_response.body
186		)
187		assert_mock ExpiringLock::REDIS
188	end
189	em :test_outbound_low_balance
190
191	def test_outbound_low_balance_top_up
192		LowBalance::AutoTopUp::CreditCardSale.expect(
193			:create,
194			EMPromise.resolve(
195				OpenStruct.new(total: 15)
196			),
197			[Customer], amount: 15
198		)
199
200		ExpiringLock::REDIS.expect(
201			:set,
202			EMPromise.resolve("OK"),
203			["jmp_customer_low_balance-customerid_topup", Time, "EX", 604800, "NX"]
204		)
205
206		CustomerFinancials::REDIS.expect(
207			:smembers,
208			EMPromise.resolve([]),
209			["block_credit_cards"]
210		)
211		LowBalance::AutoTopUp::REDIS.expect(
212			:exists,
213			0,
214			["jmp_auto_top_up_block-abcd"]
215		)
216		braintree_customer = Minitest::Mock.new
217		CustomerFinancials::BRAINTREE.expect(:customer, braintree_customer)
218		payment_methods = OpenStruct.new(payment_methods: [
219			OpenStruct.new(default?: true, unique_number_identifier: "abcd")
220		])
221		braintree_customer.expect(
222			:find,
223			EMPromise.resolve(payment_methods),
224			["customerid_topup"]
225		)
226
227		Customer::BLATHER.expect(
228			:<<,
229			nil,
230			[Blather::Stanza]
231		)
232
233		post(
234			"/outbound/calls",
235			{
236				from: "ccustomerid_topup",
237				to: "+15557654321",
238				callId: "acall"
239			}.to_json,
240			{ "CONTENT_TYPE" => "application/json" }
241		)
242
243		assert last_response.ok?
244		assert_equal(
245			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
246			"<Transfer transferCallerId=\"+15551234567\">" \
247			"<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
248			last_response.body
249		)
250		assert_mock ExpiringLock::REDIS
251		assert_mock Customer::BLATHER
252		assert_mock LowBalance::AutoTopUp::CreditCardSale
253	end
254	em :test_outbound_low_balance_top_up
255
256	def test_outbound_unsupported
257		post(
258			"/outbound/calls",
259			{
260				from: "ccustomerid",
261				to: "+95557654321",
262				callId: "acall"
263			}.to_json,
264			{ "CONTENT_TYPE" => "application/json" }
265		)
266
267		assert last_response.ok?
268		assert_equal(
269			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
270			"<SpeakSentence>The number you have dialled is not " \
271			"supported on your account.</SpeakSentence></Response>",
272			last_response.body
273		)
274	end
275	em :test_outbound_unsupported
276
277	def test_outbound_unsupported_short_numbers_911
278		post(
279			"/outbound/calls",
280			{
281				from: "ccustomerid",
282				to: "+1911",
283				callId: "acall"
284			}.to_json,
285			{ "CONTENT_TYPE" => "application/json" }
286		)
287
288		assert last_response.ok?
289		assert_equal(
290			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
291			"<SpeakSentence>The number you have dialled is not " \
292			"supported on your account.</SpeakSentence></Response>",
293			last_response.body
294		)
295	end
296	em :test_outbound_unsupported_short_numbers_911
297
298	def test_outbound_supported_9116
299		post(
300			"/outbound/calls",
301			{
302				from: "ccustomerid",
303				to: "+19116",
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			"<Transfer transferCallerId=\"+15551234567\">" \
313			"<PhoneNumber>+19116</PhoneNumber></Transfer></Response>",
314			last_response.body
315		)
316	end
317	em :test_outbound_supported_9116
318
319	def test_outbound_atlimit
320		post(
321			"/outbound/calls",
322			{
323				from: "ccustomerid_limit",
324				to: "+15557654321",
325				callId: "acall"
326			}.to_json,
327			{ "CONTENT_TYPE" => "application/json" }
328		)
329
330		assert last_response.ok?
331		assert_equal(
332			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
333			"<Gather gatherUrl=\"\/outbound/calls\" maxDigits=\"1\" " \
334			"repeatCount=\"3\"><SpeakSentence>This call will take you over " \
335			"your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
336			"Change your limit in your account settings or press 1 to accept the " \
337			"charges. You can hang up to cancel.</SpeakSentence></Gather></Response>",
338			last_response.body
339		)
340	end
341	em :test_outbound_atlimit
342
343	def test_outbound_no_customer
344		post(
345			"/outbound/calls",
346			{
347				from: "no_such_customer",
348				to: "+15557654321",
349				callId: "acall"
350			}.to_json,
351			{ "CONTENT_TYPE" => "application/json" }
352		)
353
354		assert last_response.ok?
355		assert_equal(
356			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
357			"<SpeakSentence>Your credentials are invalid, please contact support." \
358			"</SpeakSentence></Response>",
359			last_response.body
360		)
361	end
362	em :test_outbound_no_customer
363
364	def test_outbound_atlimit_digits
365		post(
366			"/outbound/calls",
367			{
368				from: "ccustomerid_limit",
369				to: "+15557654321",
370				callId: "acall",
371				digits: "1"
372			}.to_json,
373			{ "CONTENT_TYPE" => "application/json" }
374		)
375
376		assert last_response.ok?
377		assert_equal(
378			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
379			"<Transfer transferCallerId=\"+15551234567\">" \
380			"<PhoneNumber>+15557654321</PhoneNumber></Transfer></Response>",
381			last_response.body
382		)
383	end
384	em :test_outbound_atlimit_digits
385
386	def test_outbound_toll_free
387		post(
388			"/outbound/calls",
389			{
390				from: "ccustomerid",
391				to: "+18001234567",
392				callId: "acall"
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			"<Transfer transferCallerId=\"+15551234567\">" \
401			"<PhoneNumber>+18001234567</PhoneNumber></Transfer></Response>",
402			last_response.body
403		)
404	end
405	em :test_outbound_toll_free
406
407	def test_outbound_disconnect
408		post(
409			"/outbound/calls/status",
410			{
411				eventType: "disconnect",
412				from: "ccustomerid",
413				to: "+15557654321",
414				callId: "acall",
415				startTime: Time.now.to_s,
416				endTime: Time.now.to_s,
417				cause: "hangup"
418			}.to_json,
419			{ "CONTENT_TYPE" => "application/json" }
420		)
421
422		assert last_response.ok?
423		assert_equal("OK", last_response.body)
424	end
425	em :test_outbound_disconnect
426
427	def test_inbound
428		CustomerFwd::BANDWIDTH_VOICE.expect(
429			:create_call,
430			OpenStruct.new(data: OpenStruct.new(call_id: "ocall")),
431			["test_bw_account"],
432			body: Matching.new do |arg|
433				assert_equal(
434					"http://example.org/inbound/calls/acall?customer_id=customerid",
435					arg.answer_url
436				)
437			end
438		)
439
440		post(
441			"/inbound/calls",
442			{
443				from: "+15557654321",
444				to: "+15551234567",
445				callId: "acall"
446			}.to_json,
447			{ "CONTENT_TYPE" => "application/json" }
448		)
449
450		assert last_response.ok?
451		assert_equal(
452			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
453			"<Ring answerCall=\"false\" duration=\"300\" />" \
454			"</Response>",
455			last_response.body
456		)
457		assert_mock CustomerFwd::BANDWIDTH_VOICE
458	end
459	em :test_inbound
460
461	def test_inbound_from_reachability
462		CustomerFwd::BANDWIDTH_VOICE.expect(
463			:create_call,
464			OpenStruct.new(data: OpenStruct.new(call_id: "ocall")),
465			["test_bw_account"],
466			body: Matching.new do |arg|
467				assert_equal(
468					"http://example.org/inbound/calls/acall?customer_id=customerid",
469					arg.answer_url
470				)
471			end
472		)
473
474		ReachableRedis.expect(
475			:exists,
476			EMPromise.resolve(0),
477			["jmp_customer_reachability_voice-customerid"]
478		)
479
480		post(
481			"/inbound/calls",
482			{
483				from: "+14445556666",
484				to: "+15551234567",
485				callId: "acall"
486			}.to_json,
487			{ "CONTENT_TYPE" => "application/json" }
488		)
489
490		assert last_response.ok?
491		assert_equal(
492			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
493			"<Ring answerCall=\"false\" duration=\"300\" />" \
494			"</Response>",
495			last_response.body
496		)
497		assert_mock CustomerFwd::BANDWIDTH_VOICE
498		assert_mock ReachableRedis
499	end
500	em :test_inbound_from_reachability
501
502	def test_inbound_no_bwmsgsv2
503		CustomerFwd::BANDWIDTH_VOICE.expect(
504			:create_call,
505			OpenStruct.new(data: OpenStruct.new(call_id: "ocall")),
506			["test_bw_account"],
507			body: Matching.new do |arg|
508				assert_equal(
509					"http://example.org/inbound/calls/acall?customer_id=customerid2",
510					arg.answer_url
511				)
512			end
513		)
514
515		post(
516			"/inbound/calls",
517			{
518				from: "+15557654321",
519				to: "+15551230000",
520				callId: "acall"
521			}.to_json,
522			{ "CONTENT_TYPE" => "application/json" }
523		)
524
525		assert last_response.ok?
526		assert_equal(
527			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
528			"<Ring answerCall=\"false\" duration=\"300\" />" \
529			"</Response>",
530			last_response.body
531		)
532		assert_mock CustomerFwd::BANDWIDTH_VOICE
533	end
534	em :test_inbound_no_bwmsgsv2
535
536	def test_inbound_low
537		ExpiringLock::REDIS.expect(
538			:set,
539			EMPromise.resolve(nil),
540			["jmp_customer_low_balance-customerid_low", Time, "EX", 604800, "NX"]
541		)
542
543		post(
544			"/inbound/calls",
545			{
546				from: "+15557654321",
547				to: "+15551234560",
548				callId: "acall"
549			}.to_json,
550			{ "CONTENT_TYPE" => "application/json" }
551		)
552
553		assert last_response.ok?
554		assert_equal(
555			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
556			"<Redirect redirectUrl=\"/inbound/calls/acall/voicemail\" />" \
557			"</Response>",
558			last_response.body
559		)
560		assert_mock CustomerFwd::BANDWIDTH_VOICE
561		assert_mock ExpiringLock::REDIS
562	end
563	em :test_inbound_low
564
565	def test_inbound_leg2
566		post(
567			"/inbound/calls/acall?customer_id=customerid",
568			{
569				from: "+15557654321",
570				to: "sip:boop@example.com",
571				callId: "ocall"
572			}.to_json,
573			{ "CONTENT_TYPE" => "application/json" }
574		)
575
576		assert last_response.ok?
577		assert_equal(
578			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
579			"<Tag>connected</Tag><Bridge>acall</Bridge>" \
580			"</Response>",
581			last_response.body
582		)
583	end
584	em :test_inbound_leg2
585
586	def test_inbound_limit_leg2
587		path = "/inbound/calls/acall?customer_id=customerid_limit"
588
589		post(
590			path,
591			{
592				from: "+15557654321",
593				to: "sip:boop@example.com",
594				callId: "ocall"
595			}.to_json,
596			{ "CONTENT_TYPE" => "application/json" }
597		)
598
599		assert last_response.ok?
600		assert_equal(
601			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
602			"<Gather gatherUrl=\"#{path}\" maxDigits=\"1\" " \
603			"repeatCount=\"3\"><SpeakSentence>This call will take you over " \
604			"your configured monthly overage limit.</SpeakSentence><SpeakSentence>" \
605			"Change your limit in your account settings or press 1 to accept the " \
606			"charges. You can hang up to send the caller to voicemail." \
607			"</SpeakSentence></Gather></Response>",
608			last_response.body
609		)
610	end
611	em :test_inbound_limit_leg2
612
613	def test_inbound_limit_digits_leg2
614		post(
615			"/inbound/calls/acall?customer_id=customerid_limit",
616			{
617				from: "+15557654321",
618				to: "sip:boop@example.com",
619				callId: "ocall",
620				digits: "1"
621			}.to_json,
622			{ "CONTENT_TYPE" => "application/json" }
623		)
624
625		assert last_response.ok?
626		assert_equal(
627			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
628			"<Tag>connected</Tag><Bridge>acall</Bridge>" \
629			"</Response>",
630			last_response.body
631		)
632	end
633	em :test_inbound_limit_digits_leg2
634
635	def test_inbound_limit_hangup
636		Web::BANDWIDTH_VOICE.expect(
637			:modify_call,
638			nil,
639			[
640				"test_bw_account",
641				"bcall"
642			],
643			body: Matching.new do |arg|
644				assert_equal(
645					"http://example.org/inbound/calls/oocall/voicemail",
646					arg.redirect_url
647				)
648			end
649		)
650
651		post(
652			"/inbound/calls/bcall/transfer_complete",
653			{
654				from: "+15557654321",
655				to: "+15551234561",
656				callId: "oocall",
657				cause: "hangup"
658			}.to_json,
659			{ "CONTENT_TYPE" => "application/json" }
660		)
661
662		assert last_response.ok?
663		assert_mock Web::BANDWIDTH_VOICE
664	end
665	em :test_inbound_limit_hangup
666
667	def test_voicemail
668		language_id = stub_request(:post, "https://api.rev.ai/languageid/v1/jobs")
669			.with(body: {
670				metadata: {
671					media_url: "https://jmp.chat/media",
672					from_jid: "+15557654321@component",
673					customer_id: "customerid"
674				}.to_json,
675				source_config: {
676					url: "https://jmp.chat/media"
677				},
678				notification_config: {
679					url: "http://example.org/inbound/calls/CALLID/voicemail/language_id"
680				}
681			}.to_json)
682
683		Customer::BLATHER.expect(
684			:<<,
685			nil,
686			[Matching.new do |stanza|
687				assert_equal "+15557654321@component", stanza.from.to_s
688				assert_equal "customer@example.com", stanza.to.to_s
689				assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
690			end]
691		)
692
693		post(
694			"/inbound/calls/CALLID/voicemail/audio",
695			{
696				"startTime" => "2021-01-01T00:00:00Z",
697				"endTime" => "2021-01-01T00:00:06Z",
698				"mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
699				"to" => "+15551234567",
700				"from" => "+15557654321"
701			}.to_json,
702			{ "CONTENT_TYPE" => "application/json" }
703		)
704
705		assert last_response.ok?
706		assert_mock Customer::BLATHER
707		assert_requested language_id
708	end
709	em :test_voicemail
710
711	def test_anonymous_voicemail
712		language_id = stub_request(:post, "https://api.rev.ai/languageid/v1/jobs")
713			.with(body: {
714				metadata: {
715					media_url: "https://jmp.chat/media",
716					from_jid:
717						"16;phone-context=anonymous.phone-context.soprani.ca@component",
718					customer_id: "customerid"
719				}.to_json,
720				source_config: {
721					url: "https://jmp.chat/media"
722				},
723				notification_config: {
724					url: "http://example.org/inbound/calls/CALLID/voicemail/language_id"
725				}
726			}.to_json)
727
728		Customer::BLATHER.expect(
729			:<<,
730			nil,
731			[Matching.new do |stanza|
732				assert_equal(
733					"16;phone-context=anonymous.phone-context.soprani.ca@component",
734					stanza.from.to_s
735				)
736				assert_equal "customer@example.com", stanza.to.to_s
737				assert_equal "https://jmp.chat/media", OOB.find_or_create(stanza).url
738			end]
739		)
740
741		post(
742			"/inbound/calls/CALLID/voicemail/audio",
743			{
744				"startTime" => "2021-01-01T00:00:00Z",
745				"endTime" => "2021-01-01T00:00:06Z",
746				"mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
747				"to" => "+15551234567",
748				"from" => "Anonymous"
749			}.to_json,
750			{ "CONTENT_TYPE" => "application/json" }
751		)
752
753		assert last_response.ok?
754		assert_mock Customer::BLATHER
755		assert_requested language_id
756	end
757	em :test_anonymous_voicemail
758
759	def test_voicemail_short
760		post(
761			"/inbound/calls/CALLID/voicemail/audio",
762			{
763				"startTime" => "2021-01-01T00:00:00Z",
764				"endTime" => "2021-01-01T00:00:05Z"
765			}.to_json,
766			{ "CONTENT_TYPE" => "application/json" }
767		)
768
769		assert last_response.ok?
770		assert_mock Customer::BLATHER
771	end
772	em :test_voicemail_short
773
774	def test_voicemail_no_customer
775		post(
776			"/inbound/calls/CALLID/voicemail",
777			{
778				"startTime" => "2021-01-01T00:00:00Z",
779				"endTime" => "2021-01-01T00:00:06Z",
780				"mediaUrl" => "https://voice.bandwidth.com/api/v2/accounts/1/media",
781				"to" => "+15551200000",
782				"from" => "+15557654321"
783			}.to_json,
784			{ "CONTENT_TYPE" => "application/json" }
785		)
786
787		assert last_response.ok?
788		assert_equal(
789			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
790			"<SpeakSentence>The number you have dialled is not in service." \
791			"</SpeakSentence></Response>",
792			last_response.body
793		)
794	end
795	em :test_voicemail_no_customer
796
797	def test_inbound_from_reachability_during_reachability
798		ReachableRedis.expect(
799			:exists,
800			EMPromise.resolve(1),
801			["jmp_customer_reachability_voice-customerid_reach"]
802		)
803		ReachableRedis.expect(
804			:incr,
805			EMPromise.resolve(1),
806			["jmp_customer_reachability_voice-customerid_reach"]
807		)
808
809		post(
810			"/inbound/calls",
811			{
812				from: "+14445556666",
813				to: "+15551234563",
814				callId: "acall"
815			}.to_json,
816			{ "CONTENT_TYPE" => "application/json" }
817		)
818
819		assert last_response.ok?
820		assert_equal(
821			"<?xml version=\"1.0\" encoding=\"utf-8\" ?><Response>" \
822			"<Hangup />" \
823			"</Response>",
824			last_response.body
825		)
826		assert_mock CustomerFwd::BANDWIDTH_VOICE
827		assert_mock ReachableRedis
828	end
829	em :test_inbound_from_reachability_during_reachability
830end