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